1
0

ModConvert.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. /*
  2. * ModConvert.cpp
  3. * --------------
  4. * Purpose: Converting between various module formats.
  5. * Notes : Incomplete list of MPTm-only features and extensions in the old formats:
  6. * Features only available for MPTm:
  7. * - User definable tunings.
  8. * - Extended pattern range
  9. * - Extended sequence
  10. * - Multiple sequences ("songs")
  11. * - Pattern-specific time signatures
  12. * - Pattern effects :xy, S7D, S7E
  13. * - Long instrument envelopes
  14. * - Envelope release node (this was previously also usable in the IT format, but is now deprecated in that format)
  15. * - Fractional tempo
  16. * - Song-specific resampling
  17. * - Alternative tempo modes (only usable in legacy XM / IT files)
  18. *
  19. * Extended features in IT/XM/S3M (not all listed below are available in all of those formats):
  20. * - Plugins
  21. * - Extended ranges for
  22. * - Sample count
  23. * - Instrument count
  24. * - Pattern count
  25. * - Sequence size
  26. * - Row count
  27. * - Channel count
  28. * - Tempo limits
  29. * - Extended sample/instrument properties.
  30. * - MIDI mapping directives
  31. * - Version info
  32. * - Channel names
  33. * - Pattern names
  34. * - For more info, see e.g. SaveExtendedSongProperties(), SaveExtendedInstrumentProperties()
  35. * Authors: OpenMPT Devs
  36. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  37. */
  38. #include "stdafx.h"
  39. #include "Moddoc.h"
  40. #include "Mainfrm.h"
  41. #include "InputHandler.h"
  42. #include "../tracklib/SampleEdit.h"
  43. #include "../soundlib/modsmp_ctrl.h"
  44. #include "../soundlib/mod_specifications.h"
  45. #include "ModConvert.h"
  46. OPENMPT_NAMESPACE_BEGIN
  47. // Trim envelopes and remove release nodes.
  48. static void UpdateEnvelopes(InstrumentEnvelope &mptEnv, const CModSpecifications &specs, std::bitset<wNumWarnings> &warnings)
  49. {
  50. // shorten instrument envelope if necessary (for mod conversion)
  51. const uint8 envMax = specs.envelopePointsMax;
  52. #define TRIMENV(envLen) if(envLen >= envMax) { envLen = envMax - 1; warnings.set(wTrimmedEnvelopes); }
  53. if(mptEnv.size() > envMax)
  54. {
  55. mptEnv.resize(envMax);
  56. warnings.set(wTrimmedEnvelopes);
  57. }
  58. TRIMENV(mptEnv.nLoopStart);
  59. TRIMENV(mptEnv.nLoopEnd);
  60. TRIMENV(mptEnv.nSustainStart);
  61. TRIMENV(mptEnv.nSustainEnd);
  62. if(mptEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET)
  63. {
  64. if(specs.hasReleaseNode)
  65. {
  66. TRIMENV(mptEnv.nReleaseNode);
  67. } else
  68. {
  69. mptEnv.nReleaseNode = ENV_RELEASE_NODE_UNSET;
  70. warnings.set(wReleaseNode);
  71. }
  72. }
  73. #undef TRIMENV
  74. }
  75. bool CModDoc::ChangeModType(MODTYPE nNewType)
  76. {
  77. std::bitset<wNumWarnings> warnings;
  78. warnings.reset();
  79. PATTERNINDEX nResizedPatterns = 0;
  80. const MODTYPE nOldType = m_SndFile.GetType();
  81. if(nNewType == nOldType)
  82. return true;
  83. const bool oldTypeIsXM = (nOldType == MOD_TYPE_XM),
  84. oldTypeIsS3M = (nOldType == MOD_TYPE_S3M), oldTypeIsIT = (nOldType == MOD_TYPE_IT),
  85. oldTypeIsMPT = (nOldType == MOD_TYPE_MPT),
  86. oldTypeIsS3M_IT_MPT = (oldTypeIsS3M || oldTypeIsIT || oldTypeIsMPT),
  87. oldTypeIsIT_MPT = (oldTypeIsIT || oldTypeIsMPT);
  88. const bool newTypeIsMOD = (nNewType == MOD_TYPE_MOD), newTypeIsXM = (nNewType == MOD_TYPE_XM),
  89. newTypeIsS3M = (nNewType == MOD_TYPE_S3M), newTypeIsIT = (nNewType == MOD_TYPE_IT),
  90. newTypeIsMPT = (nNewType == MOD_TYPE_MPT), newTypeIsMOD_XM = (newTypeIsMOD || newTypeIsXM),
  91. newTypeIsIT_MPT = (newTypeIsIT || newTypeIsMPT);
  92. const CModSpecifications &specs = m_SndFile.GetModSpecifications(nNewType);
  93. // Check if conversion to 64 rows is necessary
  94. for(const auto &pat : m_SndFile.Patterns)
  95. {
  96. if(pat.IsValid() && pat.GetNumRows() != 64)
  97. nResizedPatterns++;
  98. }
  99. if((m_SndFile.GetNumInstruments() || nResizedPatterns) && (nNewType & (MOD_TYPE_MOD|MOD_TYPE_S3M)))
  100. {
  101. if(Reporting::Confirm(
  102. "This operation will convert all instruments to samples,\n"
  103. "and resize all patterns to 64 rows.\n"
  104. "Do you want to continue?", "Warning") != cnfYes) return false;
  105. BeginWaitCursor();
  106. CriticalSection cs;
  107. // Converting instruments to samples
  108. if(m_SndFile.GetNumInstruments())
  109. {
  110. ConvertInstrumentsToSamples();
  111. warnings.set(wInstrumentsToSamples);
  112. }
  113. // Resizing all patterns to 64 rows
  114. for(auto &pat : m_SndFile.Patterns) if(pat.IsValid() && pat.GetNumRows() != 64)
  115. {
  116. ROWINDEX origRows = pat.GetNumRows();
  117. pat.Resize(64);
  118. if(origRows < 64)
  119. {
  120. // Try to save short patterns by inserting a pattern break.
  121. pat.WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(origRows - 1).RetryNextRow());
  122. }
  123. warnings.set(wResizedPatterns);
  124. }
  125. // Removing all instrument headers from channels
  126. for(auto &chn : m_SndFile.m_PlayState.Chn)
  127. {
  128. chn.pModInstrument = nullptr;
  129. }
  130. for(INSTRUMENTINDEX nIns = 0; nIns <= m_SndFile.GetNumInstruments(); nIns++)
  131. {
  132. delete m_SndFile.Instruments[nIns];
  133. m_SndFile.Instruments[nIns] = nullptr;
  134. }
  135. m_SndFile.m_nInstruments = 0;
  136. EndWaitCursor();
  137. } //End if (((m_SndFile.m_nInstruments) || (b64)) && (nNewType & (MOD_TYPE_MOD|MOD_TYPE_S3M)))
  138. BeginWaitCursor();
  139. /////////////////////////////
  140. // Converting pattern data
  141. // When converting to MOD, get the new sample transpose setting right here so that we can compensate notes in the pattern.
  142. if(newTypeIsMOD && !oldTypeIsXM)
  143. {
  144. for(SAMPLEINDEX smp = 1; smp <= m_SndFile.GetNumSamples(); smp++)
  145. {
  146. m_SndFile.GetSample(smp).FrequencyToTranspose();
  147. }
  148. }
  149. bool onlyAmigaNotes = true;
  150. for(auto &pat : m_SndFile.Patterns) if(pat.IsValid())
  151. {
  152. // This is used for -> MOD/XM conversion
  153. std::vector<std::array<ModCommand::PARAM, MAX_EFFECTS>> effMemory(GetNumChannels());
  154. std::vector<ModCommand::VOL> volMemory(GetNumChannels(), 0);
  155. std::vector<ModCommand::INSTR> instrMemory(GetNumChannels(), 0);
  156. bool addBreak = false; // When converting to XM, avoid the E60 bug.
  157. CHANNELINDEX chn = 0;
  158. ROWINDEX row = 0;
  159. for(auto m = pat.begin(); m != pat.end(); m++, chn++)
  160. {
  161. if(chn >= GetNumChannels())
  162. {
  163. chn = 0;
  164. row++;
  165. }
  166. ModCommand::INSTR instr = m->instr;
  167. if(m->instr) instrMemory[chn] = instr;
  168. else instr = instrMemory[chn];
  169. // Deal with volume column slide memory (it's not shared with the effect column)
  170. if(oldTypeIsIT_MPT && (newTypeIsMOD_XM || newTypeIsS3M))
  171. {
  172. switch(m->volcmd)
  173. {
  174. case VOLCMD_VOLSLIDEUP:
  175. case VOLCMD_VOLSLIDEDOWN:
  176. case VOLCMD_FINEVOLUP:
  177. case VOLCMD_FINEVOLDOWN:
  178. if(m->vol == 0)
  179. m->vol = volMemory[chn];
  180. else
  181. volMemory[chn] = m->vol;
  182. break;
  183. }
  184. }
  185. // Deal with MOD/XM commands without effect memory
  186. if(oldTypeIsS3M_IT_MPT && newTypeIsMOD_XM)
  187. {
  188. switch(m->command)
  189. {
  190. // No effect memory in XM / MOD
  191. case CMD_ARPEGGIO:
  192. case CMD_S3MCMDEX:
  193. case CMD_MODCMDEX:
  194. // 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.
  195. case CMD_PORTAMENTOUP:
  196. case CMD_PORTAMENTODOWN:
  197. case CMD_VOLUMESLIDE:
  198. if(m->param == 0)
  199. m->param = effMemory[chn][m->command];
  200. else
  201. effMemory[chn][m->command] = m->param;
  202. break;
  203. }
  204. }
  205. // Adjust effect memory for MOD files
  206. if(newTypeIsMOD)
  207. {
  208. switch(m->command)
  209. {
  210. case CMD_PORTAMENTOUP:
  211. case CMD_PORTAMENTODOWN:
  212. case CMD_TONEPORTAVOL:
  213. case CMD_VIBRATOVOL:
  214. case CMD_VOLUMESLIDE:
  215. // ProTracker doesn't have effect memory for these commands, so let's try to fix them
  216. if(m->param == 0)
  217. m->param = effMemory[chn][m->command];
  218. else
  219. effMemory[chn][m->command] = m->param;
  220. break;
  221. }
  222. // Compensate for loss of transpose information
  223. if(m->IsNote() && instr && instr <= GetNumSamples())
  224. {
  225. const int newNote = m->note + m_SndFile.GetSample(instr).RelativeTone;
  226. m->note = static_cast<ModCommand::NOTE>(Clamp(newNote, specs.noteMin, specs.noteMax));
  227. }
  228. if(!m->IsAmigaNote())
  229. {
  230. onlyAmigaNotes = false;
  231. }
  232. }
  233. m->Convert(nOldType, nNewType, m_SndFile);
  234. // When converting to XM, avoid the E60 bug.
  235. if(newTypeIsXM)
  236. {
  237. switch(m->command)
  238. {
  239. case CMD_MODCMDEX:
  240. if(m->param == 0x60 && row > 0)
  241. {
  242. addBreak = true;
  243. }
  244. break;
  245. case CMD_POSITIONJUMP:
  246. case CMD_PATTERNBREAK:
  247. addBreak = false;
  248. break;
  249. }
  250. }
  251. // Fix Row Delay commands when converting between MOD/XM and S3M/IT.
  252. // FT2 only considers the rightmost command, ST3/IT only the leftmost...
  253. if((nOldType & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT)) && (nNewType & (MOD_TYPE_MOD | MOD_TYPE_XM))
  254. && m->command == CMD_MODCMDEX && (m->param & 0xF0) == 0xE0)
  255. {
  256. if(oldTypeIsIT_MPT || m->param != 0xE0)
  257. {
  258. // If the leftmost row delay command is SE0, ST3 ignores it, IT doesn't.
  259. // Delete all commands right of the first command
  260. auto p = m + 1;
  261. for(CHANNELINDEX c = chn + 1; c < m_SndFile.GetNumChannels(); c++, p++)
  262. {
  263. if(p->command == CMD_S3MCMDEX && (p->param & 0xF0) == 0xE0)
  264. {
  265. p->command = CMD_NONE;
  266. }
  267. }
  268. }
  269. } else if((nOldType & (MOD_TYPE_MOD | MOD_TYPE_XM)) && (nNewType & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT))
  270. && m->command == CMD_S3MCMDEX && (m->param & 0xF0) == 0xE0)
  271. {
  272. // Delete all commands left of the last command
  273. auto p = m - 1;
  274. for(CHANNELINDEX c = 0; c < chn; c++, p--)
  275. {
  276. if(p->command == CMD_S3MCMDEX && (p->param & 0xF0) == 0xE0)
  277. {
  278. p->command = CMD_NONE;
  279. }
  280. }
  281. }
  282. }
  283. if(addBreak)
  284. {
  285. pat.WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(pat.GetNumRows() - 1));
  286. }
  287. }
  288. ////////////////////////////////////////////////
  289. // Converting instrument / sample / etc. data
  290. // Do some sample conversion
  291. const bool newTypeHasPingPongLoops = !(newTypeIsMOD || newTypeIsS3M);
  292. for(SAMPLEINDEX smp = 1; smp <= m_SndFile.GetNumSamples(); smp++)
  293. {
  294. ModSample &sample = m_SndFile.GetSample(smp);
  295. GetSampleUndo().PrepareUndo(smp, sundo_none, "Song Conversion");
  296. // Too many samples? Only 31 samples allowed in MOD format...
  297. if(newTypeIsMOD && smp > 31 && sample.nLength > 0)
  298. {
  299. warnings.set(wMOD31Samples);
  300. }
  301. // No auto-vibrato in MOD/S3M
  302. if((newTypeIsMOD || newTypeIsS3M) && (sample.nVibDepth | sample.nVibRate | sample.nVibSweep) != 0)
  303. {
  304. warnings.set(wSampleAutoVibrato);
  305. }
  306. // No sustain loops for MOD/S3M/XM
  307. bool ignoreLoopConversion = false;
  308. if(newTypeIsMOD_XM || newTypeIsS3M)
  309. {
  310. // Sustain loops - convert to normal loops
  311. if(sample.uFlags[CHN_SUSTAINLOOP])
  312. {
  313. warnings.set(wSampleSustainLoops);
  314. // Prepare conversion to regular loop
  315. if(!newTypeHasPingPongLoops)
  316. {
  317. ignoreLoopConversion = true;
  318. if(!SampleEdit::ConvertPingPongLoop(sample, m_SndFile, true))
  319. warnings.set(wSampleBidiLoops);
  320. }
  321. }
  322. }
  323. // No ping-pong loops in MOD/S3M
  324. if(!ignoreLoopConversion && !newTypeHasPingPongLoops && sample.HasPingPongLoop())
  325. {
  326. if(!SampleEdit::ConvertPingPongLoop(sample, m_SndFile, false))
  327. warnings.set(wSampleBidiLoops);
  328. }
  329. if(newTypeIsMOD && sample.RelativeTone != 0)
  330. {
  331. warnings.set(wMODSampleFrequency);
  332. }
  333. if(!CSoundFile::SupportsOPL(nNewType) && sample.uFlags[CHN_ADLIB])
  334. {
  335. warnings.set(wAdlibInstruments);
  336. }
  337. sample.Convert(nOldType, nNewType);
  338. }
  339. for(INSTRUMENTINDEX ins = 1; ins <= m_SndFile.GetNumInstruments(); ins++)
  340. {
  341. ModInstrument *pIns = m_SndFile.Instruments[ins];
  342. if(pIns == nullptr)
  343. {
  344. continue;
  345. }
  346. // Convert IT/MPT to XM (fix instruments)
  347. if(newTypeIsXM)
  348. {
  349. for(size_t i = 0; i < std::size(pIns->NoteMap); i++)
  350. {
  351. if (pIns->NoteMap[i] && pIns->NoteMap[i] != (i + 1))
  352. {
  353. warnings.set(wBrokenNoteMap);
  354. break;
  355. }
  356. }
  357. // Convert sustain loops to sustain "points"
  358. if(pIns->VolEnv.nSustainStart != pIns->VolEnv.nSustainEnd)
  359. {
  360. warnings.set(wInstrumentSustainLoops);
  361. }
  362. if(pIns->PanEnv.nSustainStart != pIns->PanEnv.nSustainEnd)
  363. {
  364. warnings.set(wInstrumentSustainLoops);
  365. }
  366. }
  367. // Convert MPT to anything - remove instrument tunings, Pitch/Tempo Lock, filter variation
  368. if(oldTypeIsMPT)
  369. {
  370. if(pIns->pTuning != nullptr)
  371. {
  372. warnings.set(wInstrumentTuning);
  373. }
  374. if(pIns->pitchToTempoLock.GetRaw() != 0)
  375. {
  376. warnings.set(wPitchToTempoLock);
  377. }
  378. if((pIns->nCutSwing | pIns->nResSwing) != 0)
  379. {
  380. warnings.set(wFilterVariation);
  381. }
  382. }
  383. pIns->Convert(nOldType, nNewType);
  384. }
  385. if(newTypeIsMOD)
  386. {
  387. // Not supported in MOD format
  388. auto firstPat = std::find_if(m_SndFile.Order().cbegin(), m_SndFile.Order().cend(), [this](PATTERNINDEX pat) { return m_SndFile.Patterns.IsValidPat(pat); });
  389. bool firstPatValid = firstPat != m_SndFile.Order().cend();
  390. bool lossy = false;
  391. if(m_SndFile.m_nDefaultSpeed != 6)
  392. {
  393. if(firstPatValid)
  394. {
  395. m_SndFile.Patterns[*firstPat].WriteEffect(EffectWriter(CMD_SPEED, ModCommand::PARAM(m_SndFile.m_nDefaultSpeed)).RetryNextRow());
  396. }
  397. m_SndFile.m_nDefaultSpeed = 6;
  398. lossy = true;
  399. }
  400. if(m_SndFile.m_nDefaultTempo != TEMPO(125, 0))
  401. {
  402. if(firstPatValid)
  403. {
  404. m_SndFile.Patterns[*firstPat].WriteEffect(EffectWriter(CMD_TEMPO, ModCommand::PARAM(m_SndFile.m_nDefaultTempo.GetInt())).RetryNextRow());
  405. }
  406. m_SndFile.m_nDefaultTempo.Set(125);
  407. lossy = true;
  408. }
  409. if(m_SndFile.m_nDefaultGlobalVolume != MAX_GLOBAL_VOLUME || m_SndFile.m_nSamplePreAmp != 48 || m_SndFile.m_nVSTiVolume != 48)
  410. {
  411. m_SndFile.m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME;
  412. m_SndFile.m_nSamplePreAmp = 48;
  413. m_SndFile.m_nVSTiVolume = 48;
  414. lossy = true;
  415. }
  416. if(lossy)
  417. {
  418. warnings.set(wMODGlobalVars);
  419. }
  420. }
  421. // Is the "restart position" value allowed in this format?
  422. for(SEQUENCEINDEX seq = 0; seq < m_SndFile.Order.GetNumSequences(); seq++)
  423. {
  424. if(m_SndFile.Order(seq).GetRestartPos() > 0 && !specs.hasRestartPos)
  425. {
  426. // Try to fix it by placing a pattern jump command in the pattern.
  427. if(!m_SndFile.Order.RestartPosToPattern(seq))
  428. {
  429. // Couldn't fix it! :(
  430. warnings.set(wRestartPos);
  431. }
  432. }
  433. }
  434. // Fix channel settings (pan/vol)
  435. for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++)
  436. {
  437. if(newTypeIsMOD_XM || newTypeIsS3M)
  438. {
  439. if(m_SndFile.ChnSettings[nChn].nVolume != 64 || m_SndFile.ChnSettings[nChn].dwFlags[CHN_SURROUND])
  440. {
  441. m_SndFile.ChnSettings[nChn].nVolume = 64;
  442. m_SndFile.ChnSettings[nChn].dwFlags.reset(CHN_SURROUND);
  443. warnings.set(wChannelVolSurround);
  444. }
  445. }
  446. if(newTypeIsXM)
  447. {
  448. if(m_SndFile.ChnSettings[nChn].nPan != 128)
  449. {
  450. m_SndFile.ChnSettings[nChn].nPan = 128;
  451. warnings.set(wChannelPanning);
  452. }
  453. }
  454. }
  455. // Check for patterns with custom time signatures (fixing will be applied in the pattern container)
  456. if(!specs.hasPatternSignatures)
  457. {
  458. for(const auto &pat: m_SndFile.Patterns)
  459. {
  460. if(pat.GetOverrideSignature())
  461. {
  462. warnings.set(wPatternSignatures);
  463. break;
  464. }
  465. }
  466. }
  467. // Check whether the new format supports embedding the edit history in the file.
  468. if(oldTypeIsIT_MPT && !newTypeIsIT_MPT && GetSoundFile().GetFileHistory().size() > 0)
  469. {
  470. warnings.set(wEditHistory);
  471. }
  472. if((nOldType & MOD_TYPE_XM) && m_SndFile.m_playBehaviour[kFT2VolumeRamping])
  473. {
  474. warnings.set(wVolRamp);
  475. }
  476. CriticalSection cs;
  477. m_SndFile.ChangeModTypeTo(nNewType);
  478. // In case we need to update IT bidi loop handling pre-computation or loops got changed...
  479. m_SndFile.PrecomputeSampleLoops(false);
  480. // Song flags
  481. if(!(specs.songFlags & SONG_LINEARSLIDES) && m_SndFile.m_SongFlags[SONG_LINEARSLIDES])
  482. {
  483. warnings.set(wLinearSlides);
  484. }
  485. if(oldTypeIsXM && newTypeIsIT_MPT)
  486. {
  487. m_SndFile.m_SongFlags.set(SONG_ITCOMPATGXX);
  488. } else if(newTypeIsMOD && GetNumChannels() == 4 && onlyAmigaNotes)
  489. {
  490. m_SndFile.m_SongFlags.set(SONG_ISAMIGA);
  491. m_SndFile.InitAmigaResampler();
  492. }
  493. m_SndFile.m_SongFlags &= (specs.songFlags | SONG_PLAY_FLAGS);
  494. // Adjust mix levels
  495. if(newTypeIsMOD || newTypeIsS3M)
  496. {
  497. m_SndFile.SetMixLevels(MixLevels::Compatible);
  498. }
  499. if(oldTypeIsMPT && m_SndFile.GetMixLevels() != MixLevels::Compatible && m_SndFile.GetMixLevels() != MixLevels::CompatibleFT2)
  500. {
  501. warnings.set(wMixmode);
  502. }
  503. if(!specs.hasFractionalTempo && m_SndFile.m_nDefaultTempo.GetFract() != 0)
  504. {
  505. m_SndFile.m_nDefaultTempo.Set(m_SndFile.m_nDefaultTempo.GetInt(), 0);
  506. warnings.set(wFractionalTempo);
  507. }
  508. ChangeFileExtension(nNewType);
  509. // Check mod specifications
  510. Limit(m_SndFile.m_nDefaultTempo, specs.GetTempoMin(), specs.GetTempoMax());
  511. Limit(m_SndFile.m_nDefaultSpeed, specs.speedMin, specs.speedMax);
  512. for(INSTRUMENTINDEX i = 1; i <= m_SndFile.GetNumInstruments(); i++) if(m_SndFile.Instruments[i] != nullptr)
  513. {
  514. UpdateEnvelopes(m_SndFile.Instruments[i]->VolEnv, specs, warnings);
  515. UpdateEnvelopes(m_SndFile.Instruments[i]->PanEnv, specs, warnings);
  516. UpdateEnvelopes(m_SndFile.Instruments[i]->PitchEnv, specs, warnings);
  517. }
  518. // XM requires instruments, so we create them right away.
  519. if(newTypeIsXM && GetNumInstruments() == 0)
  520. {
  521. ConvertSamplesToInstruments();
  522. }
  523. // XM has no global volume
  524. if(newTypeIsXM && m_SndFile.m_nDefaultGlobalVolume != MAX_GLOBAL_VOLUME)
  525. {
  526. if(!GlobalVolumeToPattern())
  527. {
  528. warnings.set(wGlobalVolumeNotSupported);
  529. }
  530. }
  531. // Resampling is only saved in MPTM
  532. if(!newTypeIsMPT && m_SndFile.m_nResampling != SRCMODE_DEFAULT)
  533. {
  534. warnings.set(wResamplingMode);
  535. m_SndFile.m_nResampling = SRCMODE_DEFAULT;
  536. }
  537. cs.Leave();
  538. if(warnings[wResizedPatterns])
  539. {
  540. AddToLog(LogInformation, MPT_UFORMAT("{} patterns have been resized to 64 rows")(nResizedPatterns));
  541. }
  542. static constexpr struct
  543. {
  544. ConversionWarning warning;
  545. const char *mesage;
  546. } messages[] =
  547. {
  548. // Pattern warnings
  549. { wRestartPos, "Restart position is not supported by the new format." },
  550. { wPatternSignatures, "Pattern-specific time signatures are not supported by the new format." },
  551. { wChannelVolSurround, "Channel volume and surround are not supported by the new format." },
  552. { wChannelPanning, "Channel panning is not supported by the new format." },
  553. // Sample warnings
  554. { wSampleBidiLoops, "Sample bidi loops are not supported by the new format." },
  555. { wSampleSustainLoops, "New format doesn't support sample sustain loops." },
  556. { wSampleAutoVibrato, "New format doesn't support sample autovibrato." },
  557. { wMODSampleFrequency, "Sample C-5 frequencies will be lost." },
  558. { wMOD31Samples, "Samples above 31 will be lost when saving as MOD. Consider rearranging samples if there are unused slots available." },
  559. { wAdlibInstruments, "OPL instruments are not supported by this format." },
  560. // Instrument warnings
  561. { wInstrumentsToSamples, "All instruments have been converted to samples." },
  562. { wTrimmedEnvelopes, "Instrument envelopes have been shortened." },
  563. { wInstrumentSustainLoops, "Sustain loops were converted to sustain points." },
  564. { wInstrumentTuning, "Instrument tunings will be lost." },
  565. { wPitchToTempoLock, "Pitch / Tempo Lock instrument property is not supported by the new format." },
  566. { wBrokenNoteMap, "Instrument Note Mapping is not supported by the new format." },
  567. { wReleaseNode, "Instrument envelope release nodes are not supported by the new format." },
  568. { wFilterVariation, "Random filter variation is not supported by the new format." },
  569. // General warnings
  570. { wMODGlobalVars, "Default speed, tempo and global volume will be lost." },
  571. { wLinearSlides, "Linear Frequency Slides not supported by the new format." },
  572. { wEditHistory, "Edit history will not be saved in the new format." },
  573. { wMixmode, "Consider setting the mix levels to \"Compatible\" in the song properties when working with legacy formats." },
  574. { wVolRamp, "Fasttracker 2 compatible super soft volume ramping gets lost when converting XM files to another type." },
  575. { wGlobalVolumeNotSupported, "Default global volume is not supported by the new format." },
  576. { wResamplingMode, "Song-specific resampling mode is not supported by the new format." },
  577. { wFractionalTempo, "Fractional tempo is not supported by the new format." },
  578. };
  579. for(const auto &msg : messages)
  580. {
  581. if(warnings[msg.warning])
  582. AddToLog(LogInformation, mpt::ToUnicode(mpt::Charset::UTF8, msg.mesage));
  583. }
  584. SetModified();
  585. GetPatternUndo().ClearUndo();
  586. UpdateAllViews(nullptr, GeneralHint().General().ModType());
  587. EndWaitCursor();
  588. // Update effect key commands
  589. CMainFrame::GetInputHandler()->SetEffectLetters(m_SndFile.GetModSpecifications());
  590. return true;
  591. }
  592. OPENMPT_NAMESPACE_END