CleanupSong.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977
  1. /*
  2. * CleanupSong.cpp
  3. * ---------------
  4. * Purpose: Dialog for cleaning up modules (rearranging, removing unused items).
  5. * Notes : (currently none)
  6. * Authors: Olivier Lapicque
  7. * OpenMPT Devs
  8. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  9. */
  10. #include "stdafx.h"
  11. #include "Moddoc.h"
  12. #include "Mainfrm.h"
  13. #include "CleanupSong.h"
  14. #include "../common/mptStringBuffer.h"
  15. #include "../soundlib/mod_specifications.h"
  16. #include "../soundlib/modsmp_ctrl.h"
  17. #include "../tracklib/SampleEdit.h"
  18. OPENMPT_NAMESPACE_BEGIN
  19. // Default checkbox state
  20. bool CModCleanupDlg::m_CheckBoxes[kMaxCleanupOptions] =
  21. {
  22. true, false, true, true, // patterns
  23. false, false, // orders
  24. true, false, false, true, // samples
  25. true, false, // instruments
  26. true, false, // plugins
  27. false, true, // misc
  28. };
  29. // Checkbox -> Control ID LUT
  30. WORD const CModCleanupDlg::m_CleanupIDtoDlgID[kMaxCleanupOptions] =
  31. {
  32. // patterns
  33. IDC_CHK_CLEANUP_PATTERNS, IDC_CHK_REMOVE_PATTERNS,
  34. IDC_CHK_REARRANGE_PATTERNS, IDC_CHK_REMOVE_DUPLICATES,
  35. // orders
  36. IDC_CHK_MERGE_SEQUENCES, IDC_CHK_REMOVE_ORDERS,
  37. // samples
  38. IDC_CHK_CLEANUP_SAMPLES, IDC_CHK_REMOVE_SAMPLES,
  39. IDC_CHK_REARRANGE_SAMPLES, IDC_CHK_OPTIMIZE_SAMPLES,
  40. // instruments
  41. IDC_CHK_CLEANUP_INSTRUMENTS, IDC_CHK_REMOVE_INSTRUMENTS,
  42. // plugins
  43. IDC_CHK_CLEANUP_PLUGINS, IDC_CHK_REMOVE_PLUGINS,
  44. // misc
  45. IDC_CHK_RESET_VARIABLES, IDC_CHK_UNUSED_CHANNELS,
  46. };
  47. // Options that are mutually exclusive to each other
  48. CModCleanupDlg::CleanupOptions const CModCleanupDlg::m_MutuallyExclusive[CModCleanupDlg::kMaxCleanupOptions] =
  49. {
  50. // patterns
  51. kRemovePatterns, kCleanupPatterns,
  52. kRemovePatterns, kRemovePatterns,
  53. // orders
  54. kRemoveOrders, kMergeSequences,
  55. // samples
  56. kRemoveSamples, kCleanupSamples,
  57. kRemoveSamples, kRemoveSamples,
  58. // instruments
  59. kRemoveAllInstruments, kCleanupInstruments,
  60. // plugins
  61. kRemoveAllPlugins, kCleanupPlugins,
  62. // misc
  63. kNone, kNone,
  64. };
  65. ///////////////////////////////////////////////////////////////////////
  66. // CModCleanupDlg
  67. BEGIN_MESSAGE_MAP(CModCleanupDlg, CDialog)
  68. //{{AFX_MSG_MAP(CModTypeDlg)
  69. ON_COMMAND(IDC_BTN_CLEANUP_SONG, &CModCleanupDlg::OnPresetCleanupSong)
  70. ON_COMMAND(IDC_BTN_COMPO_CLEANUP, &CModCleanupDlg::OnPresetCompoCleanup)
  71. ON_COMMAND(IDC_CHK_CLEANUP_PATTERNS, &CModCleanupDlg::OnVerifyMutualExclusive)
  72. ON_COMMAND(IDC_CHK_REMOVE_PATTERNS, &CModCleanupDlg::OnVerifyMutualExclusive)
  73. ON_COMMAND(IDC_CHK_REARRANGE_PATTERNS, &CModCleanupDlg::OnVerifyMutualExclusive)
  74. ON_COMMAND(IDC_CHK_REMOVE_DUPLICATES, &CModCleanupDlg::OnVerifyMutualExclusive)
  75. ON_COMMAND(IDC_CHK_MERGE_SEQUENCES, &CModCleanupDlg::OnVerifyMutualExclusive)
  76. ON_COMMAND(IDC_CHK_REMOVE_ORDERS, &CModCleanupDlg::OnVerifyMutualExclusive)
  77. ON_COMMAND(IDC_CHK_CLEANUP_SAMPLES, &CModCleanupDlg::OnVerifyMutualExclusive)
  78. ON_COMMAND(IDC_CHK_REMOVE_SAMPLES, &CModCleanupDlg::OnVerifyMutualExclusive)
  79. ON_COMMAND(IDC_CHK_REARRANGE_SAMPLES, &CModCleanupDlg::OnVerifyMutualExclusive)
  80. ON_COMMAND(IDC_CHK_OPTIMIZE_SAMPLES, &CModCleanupDlg::OnVerifyMutualExclusive)
  81. ON_COMMAND(IDC_CHK_CLEANUP_INSTRUMENTS, &CModCleanupDlg::OnVerifyMutualExclusive)
  82. ON_COMMAND(IDC_CHK_REMOVE_INSTRUMENTS, &CModCleanupDlg::OnVerifyMutualExclusive)
  83. ON_COMMAND(IDC_CHK_CLEANUP_PLUGINS, &CModCleanupDlg::OnVerifyMutualExclusive)
  84. ON_COMMAND(IDC_CHK_REMOVE_PLUGINS, &CModCleanupDlg::OnVerifyMutualExclusive)
  85. ON_COMMAND(IDC_CHK_RESET_VARIABLES, &CModCleanupDlg::OnVerifyMutualExclusive)
  86. ON_COMMAND(IDC_CHK_UNUSED_CHANNELS, &CModCleanupDlg::OnVerifyMutualExclusive)
  87. ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &CModCleanupDlg::OnToolTipNotify)
  88. //}}AFX_MSG_MAP
  89. END_MESSAGE_MAP()
  90. BOOL CModCleanupDlg::OnInitDialog()
  91. {
  92. CDialog::OnInitDialog();
  93. for(int i = 0; i < kMaxCleanupOptions; i++)
  94. {
  95. CheckDlgButton(m_CleanupIDtoDlgID[i], (m_CheckBoxes[i]) ? BST_CHECKED : BST_UNCHECKED);
  96. }
  97. CSoundFile &sndFile = modDoc.GetSoundFile();
  98. GetDlgItem(m_CleanupIDtoDlgID[kMergeSequences])->EnableWindow((sndFile.Order.GetNumSequences() > 1) ? TRUE : FALSE);
  99. GetDlgItem(m_CleanupIDtoDlgID[kRemoveSamples])->EnableWindow((sndFile.GetNumSamples() > 0) ? TRUE : FALSE);
  100. GetDlgItem(m_CleanupIDtoDlgID[kRearrangeSamples])->EnableWindow((sndFile.GetNumSamples() > 1) ? TRUE : FALSE);
  101. GetDlgItem(m_CleanupIDtoDlgID[kCleanupInstruments])->EnableWindow((sndFile.GetNumInstruments() > 0) ? TRUE : FALSE);
  102. GetDlgItem(m_CleanupIDtoDlgID[kRemoveAllInstruments])->EnableWindow((sndFile.GetNumInstruments() > 0) ? TRUE : FALSE);
  103. EnableToolTips(TRUE);
  104. return TRUE;
  105. }
  106. void CModCleanupDlg::OnOK()
  107. {
  108. ScopedLogCapturer logcapturer(modDoc, _T("cleanup"), this);
  109. for(int i = 0; i < kMaxCleanupOptions; i++)
  110. {
  111. m_CheckBoxes[i] = IsDlgButtonChecked(m_CleanupIDtoDlgID[i]) != BST_UNCHECKED;
  112. }
  113. bool modified = false;
  114. // Orders
  115. if(m_CheckBoxes[kMergeSequences]) modified |= MergeSequences();
  116. if(m_CheckBoxes[kRemoveOrders]) modified |= RemoveAllOrders();
  117. // Patterns
  118. if(m_CheckBoxes[kRemovePatterns]) modified |= RemoveAllPatterns();
  119. if(m_CheckBoxes[kCleanupPatterns]) modified |= RemoveUnusedPatterns();
  120. if(m_CheckBoxes[kRemoveDuplicatePatterns]) modified |= RemoveDuplicatePatterns();
  121. if(m_CheckBoxes[kRearrangePatterns]) modified |= RearrangePatterns();
  122. // Instruments
  123. if(modDoc.GetNumInstruments() > 0)
  124. {
  125. if(m_CheckBoxes[kRemoveAllInstruments]) modified |= RemoveAllInstruments();
  126. if(m_CheckBoxes[kCleanupInstruments]) modified |= RemoveUnusedInstruments();
  127. }
  128. // Samples
  129. if(m_CheckBoxes[kRemoveSamples]) modified |= RemoveAllSamples();
  130. if(m_CheckBoxes[kCleanupSamples]) modified |= RemoveUnusedSamples();
  131. if(m_CheckBoxes[kOptimizeSamples]) modified |= OptimizeSamples();
  132. if(modDoc.GetNumSamples() > 1)
  133. {
  134. if(m_CheckBoxes[kRearrangeSamples]) modified |= RearrangeSamples();
  135. }
  136. // Plugins
  137. if(m_CheckBoxes[kRemoveAllPlugins]) modified |= RemoveAllPlugins();
  138. if(m_CheckBoxes[kCleanupPlugins]) modified |= RemoveUnusedPlugins();
  139. // Create samplepack
  140. if(m_CheckBoxes[kResetVariables]) modified |= ResetVariables();
  141. // Remove unused channels
  142. if(m_CheckBoxes[kCleanupChannels]) modified |= RemoveUnusedChannels();
  143. if(modified) modDoc.SetModified();
  144. modDoc.UpdateAllViews(nullptr, UpdateHint().ModType());
  145. logcapturer.ShowLog(true);
  146. CDialog::OnOK();
  147. }
  148. void CModCleanupDlg::OnVerifyMutualExclusive()
  149. {
  150. HWND hFocus = GetFocus()->m_hWnd;
  151. for(int i = 0; i < kMaxCleanupOptions; i++)
  152. {
  153. // if this item is focussed, we have just (un)checked it.
  154. if(hFocus == GetDlgItem(m_CleanupIDtoDlgID[i])->m_hWnd)
  155. {
  156. // if we just unchecked it, there's nothing to verify.
  157. if(IsDlgButtonChecked(m_CleanupIDtoDlgID[i]) == BST_UNCHECKED)
  158. return;
  159. // now we can disable all elements that are mutually exclusive.
  160. if(m_MutuallyExclusive[i] != kNone)
  161. CheckDlgButton(m_CleanupIDtoDlgID[m_MutuallyExclusive[i]], BST_UNCHECKED);
  162. // find other elements which are mutually exclusive with the selected element.
  163. for(int j = 0; j < kMaxCleanupOptions; j++)
  164. {
  165. if(m_MutuallyExclusive[j] == i)
  166. CheckDlgButton(m_CleanupIDtoDlgID[j], BST_UNCHECKED);
  167. }
  168. return;
  169. }
  170. }
  171. }
  172. void CModCleanupDlg::OnPresetCleanupSong()
  173. {
  174. // patterns
  175. CheckDlgButton(IDC_CHK_CLEANUP_PATTERNS, BST_CHECKED);
  176. CheckDlgButton(IDC_CHK_REMOVE_PATTERNS, BST_UNCHECKED);
  177. CheckDlgButton(IDC_CHK_REARRANGE_PATTERNS, BST_CHECKED);
  178. CheckDlgButton(IDC_CHK_REMOVE_DUPLICATES, BST_CHECKED);
  179. // orders
  180. CheckDlgButton(IDC_CHK_MERGE_SEQUENCES, BST_UNCHECKED);
  181. CheckDlgButton(IDC_CHK_REMOVE_ORDERS, BST_UNCHECKED);
  182. // samples
  183. CheckDlgButton(IDC_CHK_CLEANUP_SAMPLES, BST_CHECKED);
  184. CheckDlgButton(IDC_CHK_REMOVE_SAMPLES, BST_UNCHECKED);
  185. CheckDlgButton(IDC_CHK_REARRANGE_SAMPLES, BST_UNCHECKED);
  186. CheckDlgButton(IDC_CHK_OPTIMIZE_SAMPLES, BST_CHECKED);
  187. // instruments
  188. CheckDlgButton(IDC_CHK_CLEANUP_INSTRUMENTS, BST_CHECKED);
  189. CheckDlgButton(IDC_CHK_REMOVE_INSTRUMENTS, BST_UNCHECKED);
  190. // plugins
  191. CheckDlgButton(IDC_CHK_CLEANUP_PLUGINS, BST_CHECKED);
  192. CheckDlgButton(IDC_CHK_REMOVE_PLUGINS, BST_UNCHECKED);
  193. // misc
  194. CheckDlgButton(IDC_CHK_SAMPLEPACK, BST_UNCHECKED);
  195. CheckDlgButton(IDC_CHK_UNUSED_CHANNELS, BST_CHECKED);
  196. }
  197. void CModCleanupDlg::OnPresetCompoCleanup()
  198. {
  199. // patterns
  200. CheckDlgButton(IDC_CHK_CLEANUP_PATTERNS, BST_UNCHECKED);
  201. CheckDlgButton(IDC_CHK_REMOVE_PATTERNS, BST_CHECKED);
  202. CheckDlgButton(IDC_CHK_REARRANGE_PATTERNS, BST_UNCHECKED);
  203. CheckDlgButton(IDC_CHK_REMOVE_DUPLICATES, BST_UNCHECKED);
  204. // orders
  205. CheckDlgButton(IDC_CHK_MERGE_SEQUENCES, BST_UNCHECKED);
  206. CheckDlgButton(IDC_CHK_REMOVE_ORDERS, BST_CHECKED);
  207. // samples
  208. CheckDlgButton(IDC_CHK_CLEANUP_SAMPLES, BST_UNCHECKED);
  209. CheckDlgButton(IDC_CHK_REMOVE_SAMPLES, BST_UNCHECKED);
  210. CheckDlgButton(IDC_CHK_REARRANGE_SAMPLES, BST_CHECKED);
  211. CheckDlgButton(IDC_CHK_OPTIMIZE_SAMPLES, BST_UNCHECKED);
  212. // instruments
  213. CheckDlgButton(IDC_CHK_CLEANUP_INSTRUMENTS, BST_UNCHECKED);
  214. CheckDlgButton(IDC_CHK_REMOVE_INSTRUMENTS, BST_CHECKED);
  215. // plugins
  216. CheckDlgButton(IDC_CHK_CLEANUP_PLUGINS, BST_UNCHECKED);
  217. CheckDlgButton(IDC_CHK_REMOVE_PLUGINS, BST_CHECKED);
  218. // misc
  219. CheckDlgButton(IDC_CHK_SAMPLEPACK, BST_CHECKED);
  220. CheckDlgButton(IDC_CHK_UNUSED_CHANNELS, BST_CHECKED);
  221. }
  222. BOOL CModCleanupDlg::OnToolTipNotify(UINT, NMHDR *pNMHDR, LRESULT *)
  223. {
  224. TOOLTIPTEXT* pTTT = (TOOLTIPTEXT*)pNMHDR;
  225. UINT_PTR nID = pNMHDR->idFrom;
  226. if (pTTT->uFlags & TTF_IDISHWND)
  227. {
  228. // idFrom is actually the HWND of the tool
  229. nID = ::GetDlgCtrlID((HWND)nID);
  230. }
  231. LPCTSTR lpszText = nullptr;
  232. switch(nID)
  233. {
  234. // patterns
  235. case IDC_CHK_CLEANUP_PATTERNS:
  236. lpszText = _T("Remove all unused patterns and rearrange them.");
  237. break;
  238. case IDC_CHK_REMOVE_PATTERNS:
  239. lpszText = _T("Remove all patterns.");
  240. break;
  241. case IDC_CHK_REARRANGE_PATTERNS:
  242. lpszText = _T("Number the patterns given by their order in the sequence.");
  243. break;
  244. case IDC_CHK_REMOVE_DUPLICATES:
  245. lpszText = _T("Merge patterns with identical content.");
  246. break;
  247. // orders
  248. case IDC_CHK_REMOVE_ORDERS:
  249. lpszText = _T("Reset the order list.");
  250. break;
  251. case IDC_CHK_MERGE_SEQUENCES:
  252. lpszText = _T("Merge multiple sequences into one.");
  253. break;
  254. // samples
  255. case IDC_CHK_CLEANUP_SAMPLES:
  256. lpszText = _T("Remove all unused samples.");
  257. break;
  258. case IDC_CHK_REMOVE_SAMPLES:
  259. lpszText = _T("Remove all samples.");
  260. break;
  261. case IDC_CHK_REARRANGE_SAMPLES:
  262. lpszText = _T("Reorder sample list by removing empty samples.");
  263. break;
  264. case IDC_CHK_OPTIMIZE_SAMPLES:
  265. lpszText = _T("Remove unused data after the sample loop end.");
  266. break;
  267. // instruments
  268. case IDC_CHK_CLEANUP_INSTRUMENTS:
  269. lpszText = _T("Remove all unused instruments.");
  270. break;
  271. case IDC_CHK_REMOVE_INSTRUMENTS:
  272. lpszText = _T("Remove all instruments and convert them to samples.");
  273. break;
  274. // plugins
  275. case IDC_CHK_CLEANUP_PLUGINS:
  276. lpszText = _T("Remove all unused plugins.");
  277. break;
  278. case IDC_CHK_REMOVE_PLUGINS:
  279. lpszText = _T("Remove all plugins.");
  280. break;
  281. // misc
  282. case IDC_CHK_SAMPLEPACK:
  283. lpszText = _T("Convert the module to .IT and reset song / sample / instrument variables");
  284. break;
  285. case IDC_CHK_UNUSED_CHANNELS:
  286. lpszText = _T("Removes all empty pattern channels.");
  287. break;
  288. default:
  289. lpszText = _T("");
  290. break;
  291. }
  292. pTTT->lpszText = const_cast<LPTSTR>(lpszText);
  293. return TRUE;
  294. }
  295. ///////////////////////////////////////////////////////////////////////
  296. // Actual cleanup implementations
  297. bool CModCleanupDlg::RemoveDuplicatePatterns()
  298. {
  299. CSoundFile &sndFile = modDoc.GetSoundFile();
  300. const PATTERNINDEX numPatterns = sndFile.Patterns.Size();
  301. std::vector<PATTERNINDEX> patternMapping(numPatterns, PATTERNINDEX_INVALID);
  302. BeginWaitCursor();
  303. CriticalSection cs;
  304. PATTERNINDEX foundDupes = 0;
  305. for(PATTERNINDEX pat1 = 0; pat1 < numPatterns; pat1++)
  306. {
  307. if(!sndFile.Patterns.IsValidPat(pat1))
  308. continue;
  309. const CPattern &pattern1 = sndFile.Patterns[pat1];
  310. for(PATTERNINDEX pat2 = pat1 + 1; pat2 < numPatterns; pat2++)
  311. {
  312. if(!sndFile.Patterns.IsValidPat(pat2) || patternMapping[pat2] != PATTERNINDEX_INVALID)
  313. continue;
  314. const CPattern &pattern2 = sndFile.Patterns[pat2];
  315. if(pattern1 == pattern2)
  316. {
  317. modDoc.GetPatternUndo().PrepareUndo(pat2, 0, 0, pattern2.GetNumChannels(), pattern2.GetNumRows(), "Remove Duplicate Patterns", foundDupes != 0, false);
  318. sndFile.Patterns.Remove(pat2);
  319. patternMapping[pat2] = pat1;
  320. foundDupes++;
  321. }
  322. }
  323. }
  324. if(foundDupes != 0)
  325. {
  326. modDoc.AddToLog(MPT_AFORMAT("{} duplicate pattern{} merged.")(foundDupes, foundDupes == 1 ? "" : "s"));
  327. // Fix order list
  328. for(auto &order : sndFile.Order)
  329. {
  330. for(auto &pat : order)
  331. {
  332. if(pat < numPatterns && patternMapping[pat] != PATTERNINDEX_INVALID)
  333. {
  334. pat = patternMapping[pat];
  335. }
  336. }
  337. }
  338. }
  339. EndWaitCursor();
  340. return foundDupes != 0;
  341. }
  342. // Remove unused patterns
  343. bool CModCleanupDlg::RemoveUnusedPatterns()
  344. {
  345. CSoundFile &sndFile = modDoc.GetSoundFile();
  346. const PATTERNINDEX numPatterns = sndFile.Patterns.Size();
  347. std::vector<bool> patternUsed(numPatterns, false);
  348. BeginWaitCursor();
  349. // First, find all used patterns in all sequences.
  350. for(auto &order : sndFile.Order)
  351. {
  352. for(auto pat : order)
  353. {
  354. if(pat < numPatterns)
  355. {
  356. patternUsed[pat] = true;
  357. }
  358. }
  359. }
  360. // Remove all other patterns.
  361. CriticalSection cs;
  362. PATTERNINDEX numRemovedPatterns = 0;
  363. for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
  364. {
  365. if(!patternUsed[pat] && sndFile.Patterns.IsValidPat(pat))
  366. {
  367. numRemovedPatterns++;
  368. modDoc.GetPatternUndo().PrepareUndo(pat, 0, 0, sndFile.GetNumChannels(), sndFile.Patterns[pat].GetNumRows(), "Remove Unused Patterns", numRemovedPatterns != 0, false);
  369. sndFile.Patterns.Remove(pat);
  370. }
  371. }
  372. EndWaitCursor();
  373. if(numRemovedPatterns)
  374. {
  375. modDoc.AddToLog(MPT_AFORMAT("{} pattern{} removed.")(numRemovedPatterns, numRemovedPatterns == 1 ? "" : "s"));
  376. return true;
  377. }
  378. return false;
  379. }
  380. // Rearrange patterns (first pattern in order list = 0, etc...)
  381. bool CModCleanupDlg::RearrangePatterns()
  382. {
  383. CSoundFile &sndFile = modDoc.GetSoundFile();
  384. const PATTERNINDEX numPatterns = sndFile.Patterns.Size();
  385. std::vector<PATTERNINDEX> newIndex(numPatterns, PATTERNINDEX_INVALID);
  386. bool modified = false;
  387. BeginWaitCursor();
  388. CriticalSection cs;
  389. // First, find all used patterns in all sequences.
  390. PATTERNINDEX patOrder = 0;
  391. for(auto &order : sndFile.Order)
  392. {
  393. for(auto &pat : order)
  394. {
  395. if(pat < numPatterns)
  396. {
  397. if(newIndex[pat] == PATTERNINDEX_INVALID)
  398. {
  399. newIndex[pat] = patOrder++;
  400. }
  401. pat = newIndex[pat];
  402. }
  403. }
  404. }
  405. // All unused patterns are moved to the end of the pattern list.
  406. for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
  407. {
  408. PATTERNINDEX &index = newIndex[pat];
  409. if(index == PATTERNINDEX_INVALID && sndFile.Patterns.IsValidPat(pat))
  410. {
  411. index = patOrder++;
  412. }
  413. }
  414. // Also need new indices for any non-existent patterns
  415. for(auto &index : newIndex)
  416. {
  417. if(index == PATTERNINDEX_INVALID)
  418. {
  419. index = patOrder++;
  420. }
  421. }
  422. modDoc.GetPatternUndo().RearrangePatterns(newIndex);
  423. // Now rearrange the actual patterns
  424. for(PATTERNINDEX i = 0; i < static_cast<PATTERNINDEX>(newIndex.size()); i++)
  425. {
  426. PATTERNINDEX j = newIndex[i];
  427. if(i == j)
  428. continue;
  429. while(i < j)
  430. j = newIndex[j];
  431. std::swap(sndFile.Patterns[i], sndFile.Patterns[j]);
  432. modified = true;
  433. }
  434. EndWaitCursor();
  435. return modified;
  436. }
  437. // Remove unused samples
  438. bool CModCleanupDlg::RemoveUnusedSamples()
  439. {
  440. CSoundFile &sndFile = modDoc.GetSoundFile();
  441. std::vector<bool> samplesUsed(sndFile.GetNumSamples() + 1, true);
  442. BeginWaitCursor();
  443. // Check if any samples are not referenced in the patterns (sample mode) or by an instrument (instrument mode).
  444. // This doesn't check yet if a sample is referenced by an instrument, but actually unused in the patterns.
  445. for(SAMPLEINDEX smp = 1; smp <= sndFile.GetNumSamples(); smp++) if (sndFile.GetSample(smp).HasSampleData())
  446. {
  447. if(!modDoc.IsSampleUsed(smp))
  448. {
  449. samplesUsed[smp] = false;
  450. }
  451. }
  452. SAMPLEINDEX nRemoved = sndFile.RemoveSelectedSamples(samplesUsed);
  453. const SAMPLEINDEX unusedInsSamples = sndFile.DetectUnusedSamples(samplesUsed);
  454. EndWaitCursor();
  455. if(unusedInsSamples)
  456. {
  457. mpt::ustring s = MPT_UFORMAT("OpenMPT detected {} sample{} referenced by an instrument,\nbut not used in the song. Do you want to remove them?")
  458. ( unusedInsSamples
  459. , (unusedInsSamples == 1) ? U_("") : U_("s")
  460. );
  461. if(Reporting::Confirm(s, "Sample Cleanup", false, false, this) == cnfYes)
  462. {
  463. nRemoved += sndFile.RemoveSelectedSamples(samplesUsed);
  464. }
  465. }
  466. if(nRemoved > 0)
  467. {
  468. modDoc.AddToLog(LogNotification, MPT_UFORMAT("{} unused sample{} removed")(nRemoved, (nRemoved == 1) ? U_("") : U_("s")));
  469. }
  470. return (nRemoved > 0);
  471. }
  472. // Check if the stereo channels of a sample contain identical data
  473. template<typename T>
  474. static bool ComapreStereoChannels(SmpLength length, const T *sampleData)
  475. {
  476. for(SmpLength i = 0; i < length; i++, sampleData += 2)
  477. {
  478. if(sampleData[0] != sampleData[1])
  479. {
  480. return false;
  481. }
  482. }
  483. return true;
  484. }
  485. // Remove unused sample data
  486. bool CModCleanupDlg::OptimizeSamples()
  487. {
  488. CSoundFile &sndFile = modDoc.GetSoundFile();
  489. SAMPLEINDEX numLoopOpt = 0, numStereoOpt = 0;
  490. std::vector<bool> stereoOptSamples(sndFile.GetNumSamples(), false);
  491. for(SAMPLEINDEX smp = 1; smp <= sndFile.GetNumSamples(); smp++)
  492. {
  493. const ModSample &sample = sndFile.GetSample(smp);
  494. // Determine how much of the sample will be played
  495. SmpLength loopLength = sample.nLength;
  496. if(sample.uFlags[CHN_LOOP])
  497. {
  498. loopLength = sample.nLoopEnd;
  499. if(sample.uFlags[CHN_SUSTAINLOOP])
  500. {
  501. loopLength = std::max(sample.nLoopEnd, sample.nSustainEnd);
  502. }
  503. }
  504. // Check if the sample contains identical stereo channels
  505. if(sample.GetNumChannels() == 2)
  506. {
  507. bool identicalChannels = false;
  508. if(sample.GetElementarySampleSize() == 1)
  509. {
  510. identicalChannels = ComapreStereoChannels(loopLength, sample.sample8());
  511. } else if(sample.GetElementarySampleSize() == 2)
  512. {
  513. identicalChannels = ComapreStereoChannels(loopLength, sample.sample16());
  514. }
  515. if(identicalChannels)
  516. {
  517. numStereoOpt++;
  518. stereoOptSamples[smp - 1] = true;
  519. }
  520. }
  521. if(sample.HasSampleData() && sample.nLength > loopLength + 2) numLoopOpt++;
  522. }
  523. if(!numLoopOpt && !numStereoOpt) return false;
  524. std::string s;
  525. if(numLoopOpt)
  526. s = MPT_AFORMAT("{} sample{} unused data after the loop end point.\n")(numLoopOpt, (numLoopOpt == 1) ? " has" : "s have");
  527. if(numStereoOpt)
  528. s += MPT_AFORMAT("{} stereo sample{} actually mono.\n")(numStereoOpt, (numStereoOpt == 1) ? " is" : "s are");
  529. if(numLoopOpt + numStereoOpt == 1)
  530. s += "Do you want to optimize it and remove this unused data?";
  531. else
  532. s += "Do you want to optimize them and remove this unused data?";
  533. if(Reporting::Confirm(s.c_str(), "Sample Optimization", false, false, this) != cnfYes)
  534. {
  535. return false;
  536. }
  537. for(SAMPLEINDEX smp = 1; smp <= sndFile.m_nSamples; smp++)
  538. {
  539. ModSample &sample = sndFile.GetSample(smp);
  540. // Determine how much of the sample will be played
  541. SmpLength loopLength = sample.nLength;
  542. if(sample.uFlags[CHN_LOOP])
  543. {
  544. loopLength = sample.nLoopEnd;
  545. // Sustain loop is played before normal loop, and it can actually be located after the normal loop.
  546. if(sample.uFlags[CHN_SUSTAINLOOP])
  547. {
  548. loopLength = std::max(sample.nLoopEnd, sample.nSustainEnd);
  549. }
  550. }
  551. if(sample.nLength > loopLength && loopLength >= 2)
  552. {
  553. modDoc.GetSampleUndo().PrepareUndo(smp, sundo_delete, "Trim Unused Data", loopLength, sample.nLength);
  554. SampleEdit::ResizeSample(sample, loopLength, sndFile);
  555. }
  556. // Convert stereo samples with identical channels to mono
  557. if(stereoOptSamples[smp - 1])
  558. {
  559. modDoc.GetSampleUndo().PrepareUndo(smp, sundo_replace, "Mono Conversion");
  560. ctrlSmp::ConvertToMono(sample, sndFile, ctrlSmp::onlyLeft);
  561. }
  562. }
  563. if(numLoopOpt)
  564. {
  565. s = MPT_AFORMAT("{} sample loop{} optimized")(numLoopOpt, (numLoopOpt == 1) ? "" : "s");
  566. modDoc.AddToLog(s);
  567. }
  568. if(numStereoOpt)
  569. {
  570. s = MPT_AFORMAT("{} sample{} converted to mono")(numStereoOpt, (numStereoOpt == 1) ? "" : "s");
  571. modDoc.AddToLog(s);
  572. }
  573. return true;
  574. }
  575. // Rearrange sample list
  576. bool CModCleanupDlg::RearrangeSamples()
  577. {
  578. CSoundFile &sndFile = modDoc.GetSoundFile();
  579. if(sndFile.GetNumSamples() < 2)
  580. return false;
  581. std::vector<SAMPLEINDEX> sampleMap;
  582. sampleMap.reserve(sndFile.GetNumSamples());
  583. // First, find out which sample slots are unused and create the new sample map only with used samples
  584. for(SAMPLEINDEX i = 1; i <= sndFile.GetNumSamples(); i++)
  585. {
  586. if(sndFile.GetSample(i).HasSampleData())
  587. {
  588. sampleMap.push_back(i);
  589. }
  590. }
  591. // Nothing found to remove...
  592. if(sndFile.GetNumSamples() == sampleMap.size())
  593. {
  594. return false;
  595. }
  596. return (modDoc.ReArrangeSamples(sampleMap) != SAMPLEINDEX_INVALID);
  597. }
  598. // Remove unused instruments
  599. bool CModCleanupDlg::RemoveUnusedInstruments()
  600. {
  601. CSoundFile &sndFile = modDoc.GetSoundFile();
  602. if(!sndFile.GetNumInstruments())
  603. return false;
  604. deleteInstrumentSamples removeSamples = doNoDeleteAssociatedSamples;
  605. if(Reporting::Confirm("Remove samples associated with unused instruments?", "Removing unused instruments", false, false, this) == cnfYes)
  606. {
  607. removeSamples = deleteAssociatedSamples;
  608. }
  609. BeginWaitCursor();
  610. std::vector<bool> instrUsed(sndFile.GetNumInstruments());
  611. bool prevUsed = true, reorder = false;
  612. INSTRUMENTINDEX numUsed = 0, lastUsed = 1;
  613. for(INSTRUMENTINDEX i = 0; i < sndFile.GetNumInstruments(); i++)
  614. {
  615. instrUsed[i] = (modDoc.IsInstrumentUsed(i + 1));
  616. if(instrUsed[i])
  617. {
  618. numUsed++;
  619. lastUsed = i;
  620. if(!prevUsed)
  621. {
  622. reorder = true;
  623. }
  624. }
  625. prevUsed = instrUsed[i];
  626. }
  627. EndWaitCursor();
  628. if(reorder && numUsed >= 1)
  629. {
  630. reorder = (Reporting::Confirm("Do you want to reorganize the remaining instruments?", "Removing unused instruments", false, false, this) == cnfYes);
  631. } else
  632. {
  633. reorder = false;
  634. }
  635. const INSTRUMENTINDEX numRemoved = sndFile.GetNumInstruments() - numUsed;
  636. if(numRemoved != 0)
  637. {
  638. BeginWaitCursor();
  639. std::vector<INSTRUMENTINDEX> instrMap;
  640. instrMap.reserve(sndFile.GetNumInstruments());
  641. for(INSTRUMENTINDEX i = 0; i < sndFile.GetNumInstruments(); i++)
  642. {
  643. if(instrUsed[i])
  644. {
  645. instrMap.push_back(i + 1);
  646. } else if(!reorder && i < lastUsed)
  647. {
  648. instrMap.push_back(INSTRUMENTINDEX_INVALID);
  649. }
  650. }
  651. modDoc.ReArrangeInstruments(instrMap, removeSamples);
  652. EndWaitCursor();
  653. modDoc.AddToLog(LogNotification, MPT_UFORMAT("{} unused instrument{} removed")(numRemoved, (numRemoved == 1) ? U_("") : U_("s")));
  654. return true;
  655. }
  656. return false;
  657. }
  658. // Remove ununsed plugins
  659. bool CModCleanupDlg::RemoveUnusedPlugins()
  660. {
  661. CSoundFile &sndFile = modDoc.GetSoundFile();
  662. std::vector<bool> usedmap(MAX_MIXPLUGINS, false);
  663. for(PLUGINDEX nPlug = 0; nPlug < MAX_MIXPLUGINS; nPlug++)
  664. {
  665. // Is the plugin assigned to a channel?
  666. for(CHANNELINDEX nChn = 0; nChn < sndFile.GetNumChannels(); nChn++)
  667. {
  668. if (sndFile.ChnSettings[nChn].nMixPlugin == nPlug + 1)
  669. {
  670. usedmap[nPlug] = true;
  671. break;
  672. }
  673. }
  674. // Is the plugin used by an instrument?
  675. for(INSTRUMENTINDEX nIns = 1; nIns <= sndFile.GetNumInstruments(); nIns++)
  676. {
  677. if (sndFile.Instruments[nIns] && (sndFile.Instruments[nIns]->nMixPlug == nPlug + 1))
  678. {
  679. usedmap[nPlug] = true;
  680. break;
  681. }
  682. }
  683. // Is the plugin assigned to master?
  684. if(sndFile.m_MixPlugins[nPlug].IsMasterEffect())
  685. usedmap[nPlug] = true;
  686. // All outputs of used plugins count as used
  687. if(usedmap[nPlug])
  688. {
  689. if(!sndFile.m_MixPlugins[nPlug].IsOutputToMaster())
  690. {
  691. PLUGINDEX output = sndFile.m_MixPlugins[nPlug].GetOutputPlugin();
  692. if(output != PLUGINDEX_INVALID)
  693. {
  694. usedmap[output] = true;
  695. }
  696. }
  697. }
  698. }
  699. PLUGINDEX numRemoved = modDoc.RemovePlugs(usedmap);
  700. if(numRemoved != 0)
  701. {
  702. modDoc.AddToLog(LogInformation, MPT_UFORMAT("{} unused plugin{} removed")(numRemoved, (numRemoved == 1) ? U_("") : U_("s")));
  703. return true;
  704. }
  705. return false;
  706. }
  707. // Reset variables (convert to IT, reset global/smp/ins vars, etc.)
  708. bool CModCleanupDlg::ResetVariables()
  709. {
  710. CSoundFile &sndFile = modDoc.GetSoundFile();
  711. if(Reporting::Confirm(_T("OpenMPT will convert the module to IT format and reset all song, sample and instrument attributes to default values. Continue?"), _T("Resetting variables"), false, false, this) == cnfNo)
  712. return false;
  713. // Stop play.
  714. CMainFrame::GetMainFrame()->StopMod(&modDoc);
  715. BeginWaitCursor();
  716. CriticalSection cs;
  717. // Convert to IT...
  718. modDoc.ChangeModType(MOD_TYPE_IT);
  719. sndFile.SetDefaultPlaybackBehaviour(sndFile.GetType());
  720. sndFile.SetMixLevels(MixLevels::Compatible);
  721. sndFile.m_songArtist.clear();
  722. sndFile.m_nTempoMode = TempoMode::Classic;
  723. sndFile.m_SongFlags = SONG_LINEARSLIDES;
  724. sndFile.m_MidiCfg.Reset();
  725. // Global vars
  726. sndFile.m_nDefaultTempo.Set(125);
  727. sndFile.m_nDefaultSpeed = 6;
  728. sndFile.m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME;
  729. sndFile.m_nSamplePreAmp = 48;
  730. sndFile.m_nVSTiVolume = 48;
  731. sndFile.Order().SetRestartPos(0);
  732. if(sndFile.Order().empty())
  733. {
  734. modDoc.InsertPattern(64, 0);
  735. }
  736. // Reset instruments (if there are any)
  737. for(INSTRUMENTINDEX i = 1; i <= sndFile.GetNumInstruments(); i++) if(sndFile.Instruments[i])
  738. {
  739. sndFile.Instruments[i]->nFadeOut = 256;
  740. sndFile.Instruments[i]->nGlobalVol = 64;
  741. sndFile.Instruments[i]->nPan = 128;
  742. sndFile.Instruments[i]->dwFlags.reset(INS_SETPANNING);
  743. sndFile.Instruments[i]->nMixPlug = 0;
  744. sndFile.Instruments[i]->nVolSwing = 0;
  745. sndFile.Instruments[i]->nPanSwing = 0;
  746. sndFile.Instruments[i]->nCutSwing = 0;
  747. sndFile.Instruments[i]->nResSwing = 0;
  748. }
  749. for(CHANNELINDEX chn = 0; chn < sndFile.GetNumChannels(); chn++)
  750. {
  751. sndFile.InitChannel(chn);
  752. }
  753. // reset samples
  754. SampleEdit::ResetSamples(sndFile, SampleEdit::SmpResetCompo);
  755. cs.Leave();
  756. EndWaitCursor();
  757. return true;
  758. }
  759. bool CModCleanupDlg::RemoveUnusedChannels()
  760. {
  761. // Avoid M.K. modules to become xCHN modules if some channels are unused.
  762. if(modDoc.GetModType() == MOD_TYPE_MOD && modDoc.GetNumChannels() == 4)
  763. return false;
  764. std::vector<bool> usedChannels;
  765. modDoc.CheckUsedChannels(usedChannels, modDoc.GetNumChannels() - modDoc.GetSoundFile().GetModSpecifications().channelsMin);
  766. return modDoc.RemoveChannels(usedChannels);
  767. }
  768. // Remove all patterns
  769. bool CModCleanupDlg::RemoveAllPatterns()
  770. {
  771. CSoundFile &sndFile = modDoc.GetSoundFile();
  772. if(sndFile.Patterns.Size() == 0) return false;
  773. modDoc.GetPatternUndo().ClearUndo();
  774. sndFile.Patterns.ResizeArray(0);
  775. sndFile.SetCurrentOrder(0);
  776. return true;
  777. }
  778. // Remove all orders
  779. bool CModCleanupDlg::RemoveAllOrders()
  780. {
  781. CSoundFile &sndFile = modDoc.GetSoundFile();
  782. sndFile.Order.Initialize();
  783. sndFile.SetCurrentOrder(0);
  784. return true;
  785. }
  786. // Remove all samples
  787. bool CModCleanupDlg::RemoveAllSamples()
  788. {
  789. CSoundFile &sndFile = modDoc.GetSoundFile();
  790. if (sndFile.GetNumSamples() == 0) return false;
  791. std::vector<bool> keepSamples(sndFile.GetNumSamples() + 1, false);
  792. sndFile.RemoveSelectedSamples(keepSamples);
  793. SampleEdit::ResetSamples(sndFile, SampleEdit::SmpResetInit, 1, MAX_SAMPLES - 1);
  794. return true;
  795. }
  796. // Remove all instruments
  797. bool CModCleanupDlg::RemoveAllInstruments()
  798. {
  799. CSoundFile &sndFile = modDoc.GetSoundFile();
  800. if(sndFile.GetNumInstruments() == 0) return false;
  801. modDoc.ConvertInstrumentsToSamples();
  802. for(INSTRUMENTINDEX i = 1; i <= sndFile.GetNumInstruments(); i++)
  803. {
  804. sndFile.DestroyInstrument(i, doNoDeleteAssociatedSamples);
  805. }
  806. sndFile.m_nInstruments = 0;
  807. return true;
  808. }
  809. // Remove all plugins
  810. bool CModCleanupDlg::RemoveAllPlugins()
  811. {
  812. std::vector<bool> keepMask(MAX_MIXPLUGINS, false);
  813. modDoc.RemovePlugs(keepMask);
  814. return true;
  815. }
  816. bool CModCleanupDlg::MergeSequences()
  817. {
  818. return modDoc.GetSoundFile().Order.MergeSequences();
  819. }
  820. OPENMPT_NAMESPACE_END