aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/openmpt-trunk/mptrack/CleanupSong.cpp
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/mptrack/CleanupSong.cpp
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/mptrack/CleanupSong.cpp')
-rw-r--r--Src/external_dependencies/openmpt-trunk/mptrack/CleanupSong.cpp977
1 files changed, 977 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/mptrack/CleanupSong.cpp b/Src/external_dependencies/openmpt-trunk/mptrack/CleanupSong.cpp
new file mode 100644
index 00000000..6120516f
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/mptrack/CleanupSong.cpp
@@ -0,0 +1,977 @@
+/*
+ * CleanupSong.cpp
+ * ---------------
+ * Purpose: Dialog for cleaning up modules (rearranging, removing unused items).
+ * Notes : (currently none)
+ * Authors: Olivier Lapicque
+ * 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 "CleanupSong.h"
+#include "../common/mptStringBuffer.h"
+#include "../soundlib/mod_specifications.h"
+#include "../soundlib/modsmp_ctrl.h"
+#include "../tracklib/SampleEdit.h"
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+// Default checkbox state
+bool CModCleanupDlg::m_CheckBoxes[kMaxCleanupOptions] =
+{
+ true, false, true, true, // patterns
+ false, false, // orders
+ true, false, false, true, // samples
+ true, false, // instruments
+ true, false, // plugins
+ false, true, // misc
+};
+
+// Checkbox -> Control ID LUT
+WORD const CModCleanupDlg::m_CleanupIDtoDlgID[kMaxCleanupOptions] =
+{
+ // patterns
+ IDC_CHK_CLEANUP_PATTERNS, IDC_CHK_REMOVE_PATTERNS,
+ IDC_CHK_REARRANGE_PATTERNS, IDC_CHK_REMOVE_DUPLICATES,
+ // orders
+ IDC_CHK_MERGE_SEQUENCES, IDC_CHK_REMOVE_ORDERS,
+ // samples
+ IDC_CHK_CLEANUP_SAMPLES, IDC_CHK_REMOVE_SAMPLES,
+ IDC_CHK_REARRANGE_SAMPLES, IDC_CHK_OPTIMIZE_SAMPLES,
+ // instruments
+ IDC_CHK_CLEANUP_INSTRUMENTS, IDC_CHK_REMOVE_INSTRUMENTS,
+ // plugins
+ IDC_CHK_CLEANUP_PLUGINS, IDC_CHK_REMOVE_PLUGINS,
+ // misc
+ IDC_CHK_RESET_VARIABLES, IDC_CHK_UNUSED_CHANNELS,
+};
+
+// Options that are mutually exclusive to each other
+CModCleanupDlg::CleanupOptions const CModCleanupDlg::m_MutuallyExclusive[CModCleanupDlg::kMaxCleanupOptions] =
+{
+ // patterns
+ kRemovePatterns, kCleanupPatterns,
+ kRemovePatterns, kRemovePatterns,
+ // orders
+ kRemoveOrders, kMergeSequences,
+ // samples
+ kRemoveSamples, kCleanupSamples,
+ kRemoveSamples, kRemoveSamples,
+ // instruments
+ kRemoveAllInstruments, kCleanupInstruments,
+ // plugins
+ kRemoveAllPlugins, kCleanupPlugins,
+ // misc
+ kNone, kNone,
+
+};
+
+///////////////////////////////////////////////////////////////////////
+// CModCleanupDlg
+
+BEGIN_MESSAGE_MAP(CModCleanupDlg, CDialog)
+ //{{AFX_MSG_MAP(CModTypeDlg)
+ ON_COMMAND(IDC_BTN_CLEANUP_SONG, &CModCleanupDlg::OnPresetCleanupSong)
+ ON_COMMAND(IDC_BTN_COMPO_CLEANUP, &CModCleanupDlg::OnPresetCompoCleanup)
+
+ ON_COMMAND(IDC_CHK_CLEANUP_PATTERNS, &CModCleanupDlg::OnVerifyMutualExclusive)
+ ON_COMMAND(IDC_CHK_REMOVE_PATTERNS, &CModCleanupDlg::OnVerifyMutualExclusive)
+ ON_COMMAND(IDC_CHK_REARRANGE_PATTERNS, &CModCleanupDlg::OnVerifyMutualExclusive)
+ ON_COMMAND(IDC_CHK_REMOVE_DUPLICATES, &CModCleanupDlg::OnVerifyMutualExclusive)
+ ON_COMMAND(IDC_CHK_MERGE_SEQUENCES, &CModCleanupDlg::OnVerifyMutualExclusive)
+ ON_COMMAND(IDC_CHK_REMOVE_ORDERS, &CModCleanupDlg::OnVerifyMutualExclusive)
+ ON_COMMAND(IDC_CHK_CLEANUP_SAMPLES, &CModCleanupDlg::OnVerifyMutualExclusive)
+ ON_COMMAND(IDC_CHK_REMOVE_SAMPLES, &CModCleanupDlg::OnVerifyMutualExclusive)
+ ON_COMMAND(IDC_CHK_REARRANGE_SAMPLES, &CModCleanupDlg::OnVerifyMutualExclusive)
+ ON_COMMAND(IDC_CHK_OPTIMIZE_SAMPLES, &CModCleanupDlg::OnVerifyMutualExclusive)
+ ON_COMMAND(IDC_CHK_CLEANUP_INSTRUMENTS, &CModCleanupDlg::OnVerifyMutualExclusive)
+ ON_COMMAND(IDC_CHK_REMOVE_INSTRUMENTS, &CModCleanupDlg::OnVerifyMutualExclusive)
+ ON_COMMAND(IDC_CHK_CLEANUP_PLUGINS, &CModCleanupDlg::OnVerifyMutualExclusive)
+ ON_COMMAND(IDC_CHK_REMOVE_PLUGINS, &CModCleanupDlg::OnVerifyMutualExclusive)
+ ON_COMMAND(IDC_CHK_RESET_VARIABLES, &CModCleanupDlg::OnVerifyMutualExclusive)
+ ON_COMMAND(IDC_CHK_UNUSED_CHANNELS, &CModCleanupDlg::OnVerifyMutualExclusive)
+
+ ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &CModCleanupDlg::OnToolTipNotify)
+ //}}AFX_MSG_MAP
+END_MESSAGE_MAP()
+
+
+BOOL CModCleanupDlg::OnInitDialog()
+{
+ CDialog::OnInitDialog();
+ for(int i = 0; i < kMaxCleanupOptions; i++)
+ {
+ CheckDlgButton(m_CleanupIDtoDlgID[i], (m_CheckBoxes[i]) ? BST_CHECKED : BST_UNCHECKED);
+ }
+
+ CSoundFile &sndFile = modDoc.GetSoundFile();
+
+ GetDlgItem(m_CleanupIDtoDlgID[kMergeSequences])->EnableWindow((sndFile.Order.GetNumSequences() > 1) ? TRUE : FALSE);
+
+ GetDlgItem(m_CleanupIDtoDlgID[kRemoveSamples])->EnableWindow((sndFile.GetNumSamples() > 0) ? TRUE : FALSE);
+ GetDlgItem(m_CleanupIDtoDlgID[kRearrangeSamples])->EnableWindow((sndFile.GetNumSamples() > 1) ? TRUE : FALSE);
+
+ GetDlgItem(m_CleanupIDtoDlgID[kCleanupInstruments])->EnableWindow((sndFile.GetNumInstruments() > 0) ? TRUE : FALSE);
+ GetDlgItem(m_CleanupIDtoDlgID[kRemoveAllInstruments])->EnableWindow((sndFile.GetNumInstruments() > 0) ? TRUE : FALSE);
+
+ EnableToolTips(TRUE);
+ return TRUE;
+}
+
+
+void CModCleanupDlg::OnOK()
+{
+ ScopedLogCapturer logcapturer(modDoc, _T("cleanup"), this);
+ for(int i = 0; i < kMaxCleanupOptions; i++)
+ {
+ m_CheckBoxes[i] = IsDlgButtonChecked(m_CleanupIDtoDlgID[i]) != BST_UNCHECKED;
+ }
+
+ bool modified = false;
+
+ // Orders
+ if(m_CheckBoxes[kMergeSequences]) modified |= MergeSequences();
+ if(m_CheckBoxes[kRemoveOrders]) modified |= RemoveAllOrders();
+
+ // Patterns
+ if(m_CheckBoxes[kRemovePatterns]) modified |= RemoveAllPatterns();
+ if(m_CheckBoxes[kCleanupPatterns]) modified |= RemoveUnusedPatterns();
+ if(m_CheckBoxes[kRemoveDuplicatePatterns]) modified |= RemoveDuplicatePatterns();
+ if(m_CheckBoxes[kRearrangePatterns]) modified |= RearrangePatterns();
+
+ // Instruments
+ if(modDoc.GetNumInstruments() > 0)
+ {
+ if(m_CheckBoxes[kRemoveAllInstruments]) modified |= RemoveAllInstruments();
+ if(m_CheckBoxes[kCleanupInstruments]) modified |= RemoveUnusedInstruments();
+ }
+
+ // Samples
+ if(m_CheckBoxes[kRemoveSamples]) modified |= RemoveAllSamples();
+ if(m_CheckBoxes[kCleanupSamples]) modified |= RemoveUnusedSamples();
+ if(m_CheckBoxes[kOptimizeSamples]) modified |= OptimizeSamples();
+ if(modDoc.GetNumSamples() > 1)
+ {
+ if(m_CheckBoxes[kRearrangeSamples]) modified |= RearrangeSamples();
+ }
+
+ // Plugins
+ if(m_CheckBoxes[kRemoveAllPlugins]) modified |= RemoveAllPlugins();
+ if(m_CheckBoxes[kCleanupPlugins]) modified |= RemoveUnusedPlugins();
+
+ // Create samplepack
+ if(m_CheckBoxes[kResetVariables]) modified |= ResetVariables();
+
+ // Remove unused channels
+ if(m_CheckBoxes[kCleanupChannels]) modified |= RemoveUnusedChannels();
+
+ if(modified) modDoc.SetModified();
+ modDoc.UpdateAllViews(nullptr, UpdateHint().ModType());
+ logcapturer.ShowLog(true);
+ CDialog::OnOK();
+}
+
+
+void CModCleanupDlg::OnVerifyMutualExclusive()
+{
+ HWND hFocus = GetFocus()->m_hWnd;
+ for(int i = 0; i < kMaxCleanupOptions; i++)
+ {
+ // if this item is focussed, we have just (un)checked it.
+ if(hFocus == GetDlgItem(m_CleanupIDtoDlgID[i])->m_hWnd)
+ {
+ // if we just unchecked it, there's nothing to verify.
+ if(IsDlgButtonChecked(m_CleanupIDtoDlgID[i]) == BST_UNCHECKED)
+ return;
+
+ // now we can disable all elements that are mutually exclusive.
+ if(m_MutuallyExclusive[i] != kNone)
+ CheckDlgButton(m_CleanupIDtoDlgID[m_MutuallyExclusive[i]], BST_UNCHECKED);
+ // find other elements which are mutually exclusive with the selected element.
+ for(int j = 0; j < kMaxCleanupOptions; j++)
+ {
+ if(m_MutuallyExclusive[j] == i)
+ CheckDlgButton(m_CleanupIDtoDlgID[j], BST_UNCHECKED);
+ }
+ return;
+ }
+ }
+}
+
+
+void CModCleanupDlg::OnPresetCleanupSong()
+{
+ // patterns
+ CheckDlgButton(IDC_CHK_CLEANUP_PATTERNS, BST_CHECKED);
+ CheckDlgButton(IDC_CHK_REMOVE_PATTERNS, BST_UNCHECKED);
+ CheckDlgButton(IDC_CHK_REARRANGE_PATTERNS, BST_CHECKED);
+ CheckDlgButton(IDC_CHK_REMOVE_DUPLICATES, BST_CHECKED);
+ // orders
+ CheckDlgButton(IDC_CHK_MERGE_SEQUENCES, BST_UNCHECKED);
+ CheckDlgButton(IDC_CHK_REMOVE_ORDERS, BST_UNCHECKED);
+ // samples
+ CheckDlgButton(IDC_CHK_CLEANUP_SAMPLES, BST_CHECKED);
+ CheckDlgButton(IDC_CHK_REMOVE_SAMPLES, BST_UNCHECKED);
+ CheckDlgButton(IDC_CHK_REARRANGE_SAMPLES, BST_UNCHECKED);
+ CheckDlgButton(IDC_CHK_OPTIMIZE_SAMPLES, BST_CHECKED);
+ // instruments
+ CheckDlgButton(IDC_CHK_CLEANUP_INSTRUMENTS, BST_CHECKED);
+ CheckDlgButton(IDC_CHK_REMOVE_INSTRUMENTS, BST_UNCHECKED);
+ // plugins
+ CheckDlgButton(IDC_CHK_CLEANUP_PLUGINS, BST_CHECKED);
+ CheckDlgButton(IDC_CHK_REMOVE_PLUGINS, BST_UNCHECKED);
+ // misc
+ CheckDlgButton(IDC_CHK_SAMPLEPACK, BST_UNCHECKED);
+ CheckDlgButton(IDC_CHK_UNUSED_CHANNELS, BST_CHECKED);
+}
+
+
+void CModCleanupDlg::OnPresetCompoCleanup()
+{
+ // patterns
+ CheckDlgButton(IDC_CHK_CLEANUP_PATTERNS, BST_UNCHECKED);
+ CheckDlgButton(IDC_CHK_REMOVE_PATTERNS, BST_CHECKED);
+ CheckDlgButton(IDC_CHK_REARRANGE_PATTERNS, BST_UNCHECKED);
+ CheckDlgButton(IDC_CHK_REMOVE_DUPLICATES, BST_UNCHECKED);
+ // orders
+ CheckDlgButton(IDC_CHK_MERGE_SEQUENCES, BST_UNCHECKED);
+ CheckDlgButton(IDC_CHK_REMOVE_ORDERS, BST_CHECKED);
+ // samples
+ CheckDlgButton(IDC_CHK_CLEANUP_SAMPLES, BST_UNCHECKED);
+ CheckDlgButton(IDC_CHK_REMOVE_SAMPLES, BST_UNCHECKED);
+ CheckDlgButton(IDC_CHK_REARRANGE_SAMPLES, BST_CHECKED);
+ CheckDlgButton(IDC_CHK_OPTIMIZE_SAMPLES, BST_UNCHECKED);
+ // instruments
+ CheckDlgButton(IDC_CHK_CLEANUP_INSTRUMENTS, BST_UNCHECKED);
+ CheckDlgButton(IDC_CHK_REMOVE_INSTRUMENTS, BST_CHECKED);
+ // plugins
+ CheckDlgButton(IDC_CHK_CLEANUP_PLUGINS, BST_UNCHECKED);
+ CheckDlgButton(IDC_CHK_REMOVE_PLUGINS, BST_CHECKED);
+ // misc
+ CheckDlgButton(IDC_CHK_SAMPLEPACK, BST_CHECKED);
+ CheckDlgButton(IDC_CHK_UNUSED_CHANNELS, BST_CHECKED);
+}
+
+
+BOOL CModCleanupDlg::OnToolTipNotify(UINT, NMHDR *pNMHDR, LRESULT *)
+{
+ TOOLTIPTEXT* pTTT = (TOOLTIPTEXT*)pNMHDR;
+ UINT_PTR nID = pNMHDR->idFrom;
+ if (pTTT->uFlags & TTF_IDISHWND)
+ {
+ // idFrom is actually the HWND of the tool
+ nID = ::GetDlgCtrlID((HWND)nID);
+ }
+
+ LPCTSTR lpszText = nullptr;
+ switch(nID)
+ {
+ // patterns
+ case IDC_CHK_CLEANUP_PATTERNS:
+ lpszText = _T("Remove all unused patterns and rearrange them.");
+ break;
+ case IDC_CHK_REMOVE_PATTERNS:
+ lpszText = _T("Remove all patterns.");
+ break;
+ case IDC_CHK_REARRANGE_PATTERNS:
+ lpszText = _T("Number the patterns given by their order in the sequence.");
+ break;
+ case IDC_CHK_REMOVE_DUPLICATES:
+ lpszText = _T("Merge patterns with identical content.");
+ break;
+ // orders
+ case IDC_CHK_REMOVE_ORDERS:
+ lpszText = _T("Reset the order list.");
+ break;
+ case IDC_CHK_MERGE_SEQUENCES:
+ lpszText = _T("Merge multiple sequences into one.");
+ break;
+ // samples
+ case IDC_CHK_CLEANUP_SAMPLES:
+ lpszText = _T("Remove all unused samples.");
+ break;
+ case IDC_CHK_REMOVE_SAMPLES:
+ lpszText = _T("Remove all samples.");
+ break;
+ case IDC_CHK_REARRANGE_SAMPLES:
+ lpszText = _T("Reorder sample list by removing empty samples.");
+ break;
+ case IDC_CHK_OPTIMIZE_SAMPLES:
+ lpszText = _T("Remove unused data after the sample loop end.");
+ break;
+ // instruments
+ case IDC_CHK_CLEANUP_INSTRUMENTS:
+ lpszText = _T("Remove all unused instruments.");
+ break;
+ case IDC_CHK_REMOVE_INSTRUMENTS:
+ lpszText = _T("Remove all instruments and convert them to samples.");
+ break;
+ // plugins
+ case IDC_CHK_CLEANUP_PLUGINS:
+ lpszText = _T("Remove all unused plugins.");
+ break;
+ case IDC_CHK_REMOVE_PLUGINS:
+ lpszText = _T("Remove all plugins.");
+ break;
+ // misc
+ case IDC_CHK_SAMPLEPACK:
+ lpszText = _T("Convert the module to .IT and reset song / sample / instrument variables");
+ break;
+ case IDC_CHK_UNUSED_CHANNELS:
+ lpszText = _T("Removes all empty pattern channels.");
+ break;
+ default:
+ lpszText = _T("");
+ break;
+ }
+ pTTT->lpszText = const_cast<LPTSTR>(lpszText);
+ return TRUE;
+}
+
+
+///////////////////////////////////////////////////////////////////////
+// Actual cleanup implementations
+
+bool CModCleanupDlg::RemoveDuplicatePatterns()
+{
+ CSoundFile &sndFile = modDoc.GetSoundFile();
+ const PATTERNINDEX numPatterns = sndFile.Patterns.Size();
+ std::vector<PATTERNINDEX> patternMapping(numPatterns, PATTERNINDEX_INVALID);
+
+ BeginWaitCursor();
+ CriticalSection cs;
+
+ PATTERNINDEX foundDupes = 0;
+ for(PATTERNINDEX pat1 = 0; pat1 < numPatterns; pat1++)
+ {
+ if(!sndFile.Patterns.IsValidPat(pat1))
+ continue;
+ const CPattern &pattern1 = sndFile.Patterns[pat1];
+ for(PATTERNINDEX pat2 = pat1 + 1; pat2 < numPatterns; pat2++)
+ {
+ if(!sndFile.Patterns.IsValidPat(pat2) || patternMapping[pat2] != PATTERNINDEX_INVALID)
+ continue;
+ const CPattern &pattern2 = sndFile.Patterns[pat2];
+ if(pattern1 == pattern2)
+ {
+ modDoc.GetPatternUndo().PrepareUndo(pat2, 0, 0, pattern2.GetNumChannels(), pattern2.GetNumRows(), "Remove Duplicate Patterns", foundDupes != 0, false);
+ sndFile.Patterns.Remove(pat2);
+
+ patternMapping[pat2] = pat1;
+ foundDupes++;
+ }
+ }
+ }
+
+ if(foundDupes != 0)
+ {
+ modDoc.AddToLog(MPT_AFORMAT("{} duplicate pattern{} merged.")(foundDupes, foundDupes == 1 ? "" : "s"));
+
+ // Fix order list
+ for(auto &order : sndFile.Order)
+ {
+ for(auto &pat : order)
+ {
+ if(pat < numPatterns && patternMapping[pat] != PATTERNINDEX_INVALID)
+ {
+ pat = patternMapping[pat];
+ }
+ }
+ }
+ }
+
+ EndWaitCursor();
+
+ return foundDupes != 0;
+}
+
+
+// Remove unused patterns
+bool CModCleanupDlg::RemoveUnusedPatterns()
+{
+ CSoundFile &sndFile = modDoc.GetSoundFile();
+ const PATTERNINDEX numPatterns = sndFile.Patterns.Size();
+ std::vector<bool> patternUsed(numPatterns, false);
+
+ BeginWaitCursor();
+ // First, find all used patterns in all sequences.
+ for(auto &order : sndFile.Order)
+ {
+ for(auto pat : order)
+ {
+ if(pat < numPatterns)
+ {
+ patternUsed[pat] = true;
+ }
+ }
+ }
+
+ // Remove all other patterns.
+ CriticalSection cs;
+ PATTERNINDEX numRemovedPatterns = 0;
+ for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
+ {
+ if(!patternUsed[pat] && sndFile.Patterns.IsValidPat(pat))
+ {
+ numRemovedPatterns++;
+ modDoc.GetPatternUndo().PrepareUndo(pat, 0, 0, sndFile.GetNumChannels(), sndFile.Patterns[pat].GetNumRows(), "Remove Unused Patterns", numRemovedPatterns != 0, false);
+ sndFile.Patterns.Remove(pat);
+ }
+ }
+ EndWaitCursor();
+
+ if(numRemovedPatterns)
+ {
+ modDoc.AddToLog(MPT_AFORMAT("{} pattern{} removed.")(numRemovedPatterns, numRemovedPatterns == 1 ? "" : "s"));
+ return true;
+ }
+ return false;
+}
+
+
+// Rearrange patterns (first pattern in order list = 0, etc...)
+bool CModCleanupDlg::RearrangePatterns()
+{
+ CSoundFile &sndFile = modDoc.GetSoundFile();
+
+ const PATTERNINDEX numPatterns = sndFile.Patterns.Size();
+ std::vector<PATTERNINDEX> newIndex(numPatterns, PATTERNINDEX_INVALID);
+
+ bool modified = false;
+
+ BeginWaitCursor();
+ CriticalSection cs;
+
+ // First, find all used patterns in all sequences.
+ PATTERNINDEX patOrder = 0;
+ for(auto &order : sndFile.Order)
+ {
+ for(auto &pat : order)
+ {
+ if(pat < numPatterns)
+ {
+ if(newIndex[pat] == PATTERNINDEX_INVALID)
+ {
+ newIndex[pat] = patOrder++;
+ }
+ pat = newIndex[pat];
+ }
+ }
+ }
+ // All unused patterns are moved to the end of the pattern list.
+ for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
+ {
+ PATTERNINDEX &index = newIndex[pat];
+ if(index == PATTERNINDEX_INVALID && sndFile.Patterns.IsValidPat(pat))
+ {
+ index = patOrder++;
+ }
+ }
+ // Also need new indices for any non-existent patterns
+ for(auto &index : newIndex)
+ {
+ if(index == PATTERNINDEX_INVALID)
+ {
+ index = patOrder++;
+ }
+ }
+
+ modDoc.GetPatternUndo().RearrangePatterns(newIndex);
+
+ // Now rearrange the actual patterns
+ for(PATTERNINDEX i = 0; i < static_cast<PATTERNINDEX>(newIndex.size()); i++)
+ {
+ PATTERNINDEX j = newIndex[i];
+ if(i == j)
+ continue;
+ while(i < j)
+ j = newIndex[j];
+ std::swap(sndFile.Patterns[i], sndFile.Patterns[j]);
+ modified = true;
+ }
+
+ EndWaitCursor();
+
+ return modified;
+}
+
+
+// Remove unused samples
+bool CModCleanupDlg::RemoveUnusedSamples()
+{
+ CSoundFile &sndFile = modDoc.GetSoundFile();
+
+ std::vector<bool> samplesUsed(sndFile.GetNumSamples() + 1, true);
+
+ BeginWaitCursor();
+
+ // Check if any samples are not referenced in the patterns (sample mode) or by an instrument (instrument mode).
+ // This doesn't check yet if a sample is referenced by an instrument, but actually unused in the patterns.
+ for(SAMPLEINDEX smp = 1; smp <= sndFile.GetNumSamples(); smp++) if (sndFile.GetSample(smp).HasSampleData())
+ {
+ if(!modDoc.IsSampleUsed(smp))
+ {
+ samplesUsed[smp] = false;
+ }
+ }
+
+ SAMPLEINDEX nRemoved = sndFile.RemoveSelectedSamples(samplesUsed);
+
+ const SAMPLEINDEX unusedInsSamples = sndFile.DetectUnusedSamples(samplesUsed);
+
+ EndWaitCursor();
+
+ if(unusedInsSamples)
+ {
+ mpt::ustring s = MPT_UFORMAT("OpenMPT detected {} sample{} referenced by an instrument,\nbut not used in the song. Do you want to remove them?")
+ ( unusedInsSamples
+ , (unusedInsSamples == 1) ? U_("") : U_("s")
+ );
+ if(Reporting::Confirm(s, "Sample Cleanup", false, false, this) == cnfYes)
+ {
+ nRemoved += sndFile.RemoveSelectedSamples(samplesUsed);
+ }
+ }
+
+ if(nRemoved > 0)
+ {
+ modDoc.AddToLog(LogNotification, MPT_UFORMAT("{} unused sample{} removed")(nRemoved, (nRemoved == 1) ? U_("") : U_("s")));
+ }
+
+ return (nRemoved > 0);
+}
+
+
+// Check if the stereo channels of a sample contain identical data
+template<typename T>
+static bool ComapreStereoChannels(SmpLength length, const T *sampleData)
+{
+ for(SmpLength i = 0; i < length; i++, sampleData += 2)
+ {
+ if(sampleData[0] != sampleData[1])
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Remove unused sample data
+bool CModCleanupDlg::OptimizeSamples()
+{
+ CSoundFile &sndFile = modDoc.GetSoundFile();
+
+ SAMPLEINDEX numLoopOpt = 0, numStereoOpt = 0;
+ std::vector<bool> stereoOptSamples(sndFile.GetNumSamples(), false);
+
+ for(SAMPLEINDEX smp = 1; smp <= sndFile.GetNumSamples(); smp++)
+ {
+ const ModSample &sample = sndFile.GetSample(smp);
+
+ // Determine how much of the sample will be played
+ SmpLength loopLength = sample.nLength;
+ if(sample.uFlags[CHN_LOOP])
+ {
+ loopLength = sample.nLoopEnd;
+ if(sample.uFlags[CHN_SUSTAINLOOP])
+ {
+ loopLength = std::max(sample.nLoopEnd, sample.nSustainEnd);
+ }
+ }
+
+ // Check if the sample contains identical stereo channels
+ if(sample.GetNumChannels() == 2)
+ {
+ bool identicalChannels = false;
+ if(sample.GetElementarySampleSize() == 1)
+ {
+ identicalChannels = ComapreStereoChannels(loopLength, sample.sample8());
+ } else if(sample.GetElementarySampleSize() == 2)
+ {
+ identicalChannels = ComapreStereoChannels(loopLength, sample.sample16());
+ }
+ if(identicalChannels)
+ {
+ numStereoOpt++;
+ stereoOptSamples[smp - 1] = true;
+ }
+ }
+
+ if(sample.HasSampleData() && sample.nLength > loopLength + 2) numLoopOpt++;
+ }
+ if(!numLoopOpt && !numStereoOpt) return false;
+
+ std::string s;
+ if(numLoopOpt)
+ s = MPT_AFORMAT("{} sample{} unused data after the loop end point.\n")(numLoopOpt, (numLoopOpt == 1) ? " has" : "s have");
+ if(numStereoOpt)
+ s += MPT_AFORMAT("{} stereo sample{} actually mono.\n")(numStereoOpt, (numStereoOpt == 1) ? " is" : "s are");
+ if(numLoopOpt + numStereoOpt == 1)
+ s += "Do you want to optimize it and remove this unused data?";
+ else
+ s += "Do you want to optimize them and remove this unused data?";
+
+ if(Reporting::Confirm(s.c_str(), "Sample Optimization", false, false, this) != cnfYes)
+ {
+ return false;
+ }
+
+ for(SAMPLEINDEX smp = 1; smp <= sndFile.m_nSamples; smp++)
+ {
+ ModSample &sample = sndFile.GetSample(smp);
+
+ // Determine how much of the sample will be played
+ SmpLength loopLength = sample.nLength;
+ if(sample.uFlags[CHN_LOOP])
+ {
+ loopLength = sample.nLoopEnd;
+
+ // Sustain loop is played before normal loop, and it can actually be located after the normal loop.
+ if(sample.uFlags[CHN_SUSTAINLOOP])
+ {
+ loopLength = std::max(sample.nLoopEnd, sample.nSustainEnd);
+ }
+ }
+
+ if(sample.nLength > loopLength && loopLength >= 2)
+ {
+ modDoc.GetSampleUndo().PrepareUndo(smp, sundo_delete, "Trim Unused Data", loopLength, sample.nLength);
+ SampleEdit::ResizeSample(sample, loopLength, sndFile);
+ }
+
+ // Convert stereo samples with identical channels to mono
+ if(stereoOptSamples[smp - 1])
+ {
+ modDoc.GetSampleUndo().PrepareUndo(smp, sundo_replace, "Mono Conversion");
+ ctrlSmp::ConvertToMono(sample, sndFile, ctrlSmp::onlyLeft);
+ }
+ }
+ if(numLoopOpt)
+ {
+ s = MPT_AFORMAT("{} sample loop{} optimized")(numLoopOpt, (numLoopOpt == 1) ? "" : "s");
+ modDoc.AddToLog(s);
+ }
+ if(numStereoOpt)
+ {
+ s = MPT_AFORMAT("{} sample{} converted to mono")(numStereoOpt, (numStereoOpt == 1) ? "" : "s");
+ modDoc.AddToLog(s);
+ }
+ return true;
+}
+
+// Rearrange sample list
+bool CModCleanupDlg::RearrangeSamples()
+{
+ CSoundFile &sndFile = modDoc.GetSoundFile();
+ if(sndFile.GetNumSamples() < 2)
+ return false;
+
+ std::vector<SAMPLEINDEX> sampleMap;
+ sampleMap.reserve(sndFile.GetNumSamples());
+
+ // First, find out which sample slots are unused and create the new sample map only with used samples
+ for(SAMPLEINDEX i = 1; i <= sndFile.GetNumSamples(); i++)
+ {
+ if(sndFile.GetSample(i).HasSampleData())
+ {
+ sampleMap.push_back(i);
+ }
+ }
+
+ // Nothing found to remove...
+ if(sndFile.GetNumSamples() == sampleMap.size())
+ {
+ return false;
+ }
+
+ return (modDoc.ReArrangeSamples(sampleMap) != SAMPLEINDEX_INVALID);
+}
+
+
+// Remove unused instruments
+bool CModCleanupDlg::RemoveUnusedInstruments()
+{
+ CSoundFile &sndFile = modDoc.GetSoundFile();
+ if(!sndFile.GetNumInstruments())
+ return false;
+
+ deleteInstrumentSamples removeSamples = doNoDeleteAssociatedSamples;
+ if(Reporting::Confirm("Remove samples associated with unused instruments?", "Removing unused instruments", false, false, this) == cnfYes)
+ {
+ removeSamples = deleteAssociatedSamples;
+ }
+
+ BeginWaitCursor();
+
+ std::vector<bool> instrUsed(sndFile.GetNumInstruments());
+ bool prevUsed = true, reorder = false;
+ INSTRUMENTINDEX numUsed = 0, lastUsed = 1;
+ for(INSTRUMENTINDEX i = 0; i < sndFile.GetNumInstruments(); i++)
+ {
+ instrUsed[i] = (modDoc.IsInstrumentUsed(i + 1));
+ if(instrUsed[i])
+ {
+ numUsed++;
+ lastUsed = i;
+ if(!prevUsed)
+ {
+ reorder = true;
+ }
+ }
+ prevUsed = instrUsed[i];
+ }
+
+ EndWaitCursor();
+
+ if(reorder && numUsed >= 1)
+ {
+ reorder = (Reporting::Confirm("Do you want to reorganize the remaining instruments?", "Removing unused instruments", false, false, this) == cnfYes);
+ } else
+ {
+ reorder = false;
+ }
+
+ const INSTRUMENTINDEX numRemoved = sndFile.GetNumInstruments() - numUsed;
+
+ if(numRemoved != 0)
+ {
+ BeginWaitCursor();
+
+ std::vector<INSTRUMENTINDEX> instrMap;
+ instrMap.reserve(sndFile.GetNumInstruments());
+ for(INSTRUMENTINDEX i = 0; i < sndFile.GetNumInstruments(); i++)
+ {
+ if(instrUsed[i])
+ {
+ instrMap.push_back(i + 1);
+ } else if(!reorder && i < lastUsed)
+ {
+ instrMap.push_back(INSTRUMENTINDEX_INVALID);
+ }
+ }
+
+ modDoc.ReArrangeInstruments(instrMap, removeSamples);
+
+ EndWaitCursor();
+
+ modDoc.AddToLog(LogNotification, MPT_UFORMAT("{} unused instrument{} removed")(numRemoved, (numRemoved == 1) ? U_("") : U_("s")));
+ return true;
+ }
+ return false;
+}
+
+
+// Remove ununsed plugins
+bool CModCleanupDlg::RemoveUnusedPlugins()
+{
+ CSoundFile &sndFile = modDoc.GetSoundFile();
+
+ std::vector<bool> usedmap(MAX_MIXPLUGINS, false);
+
+ for(PLUGINDEX nPlug = 0; nPlug < MAX_MIXPLUGINS; nPlug++)
+ {
+
+ // Is the plugin assigned to a channel?
+ for(CHANNELINDEX nChn = 0; nChn < sndFile.GetNumChannels(); nChn++)
+ {
+ if (sndFile.ChnSettings[nChn].nMixPlugin == nPlug + 1)
+ {
+ usedmap[nPlug] = true;
+ break;
+ }
+ }
+
+ // Is the plugin used by an instrument?
+ for(INSTRUMENTINDEX nIns = 1; nIns <= sndFile.GetNumInstruments(); nIns++)
+ {
+ if (sndFile.Instruments[nIns] && (sndFile.Instruments[nIns]->nMixPlug == nPlug + 1))
+ {
+ usedmap[nPlug] = true;
+ break;
+ }
+ }
+
+ // Is the plugin assigned to master?
+ if(sndFile.m_MixPlugins[nPlug].IsMasterEffect())
+ usedmap[nPlug] = true;
+
+ // All outputs of used plugins count as used
+ if(usedmap[nPlug])
+ {
+ if(!sndFile.m_MixPlugins[nPlug].IsOutputToMaster())
+ {
+ PLUGINDEX output = sndFile.m_MixPlugins[nPlug].GetOutputPlugin();
+ if(output != PLUGINDEX_INVALID)
+ {
+ usedmap[output] = true;
+ }
+ }
+ }
+
+ }
+
+ PLUGINDEX numRemoved = modDoc.RemovePlugs(usedmap);
+ if(numRemoved != 0)
+ {
+ modDoc.AddToLog(LogInformation, MPT_UFORMAT("{} unused plugin{} removed")(numRemoved, (numRemoved == 1) ? U_("") : U_("s")));
+ return true;
+ }
+ return false;
+}
+
+
+// Reset variables (convert to IT, reset global/smp/ins vars, etc.)
+bool CModCleanupDlg::ResetVariables()
+{
+ CSoundFile &sndFile = modDoc.GetSoundFile();
+
+ 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)
+ return false;
+
+ // Stop play.
+ CMainFrame::GetMainFrame()->StopMod(&modDoc);
+
+ BeginWaitCursor();
+ CriticalSection cs;
+
+ // Convert to IT...
+ modDoc.ChangeModType(MOD_TYPE_IT);
+ sndFile.SetDefaultPlaybackBehaviour(sndFile.GetType());
+ sndFile.SetMixLevels(MixLevels::Compatible);
+ sndFile.m_songArtist.clear();
+ sndFile.m_nTempoMode = TempoMode::Classic;
+ sndFile.m_SongFlags = SONG_LINEARSLIDES;
+ sndFile.m_MidiCfg.Reset();
+
+ // Global vars
+ sndFile.m_nDefaultTempo.Set(125);
+ sndFile.m_nDefaultSpeed = 6;
+ sndFile.m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME;
+ sndFile.m_nSamplePreAmp = 48;
+ sndFile.m_nVSTiVolume = 48;
+ sndFile.Order().SetRestartPos(0);
+
+ if(sndFile.Order().empty())
+ {
+ modDoc.InsertPattern(64, 0);
+ }
+
+ // Reset instruments (if there are any)
+ for(INSTRUMENTINDEX i = 1; i <= sndFile.GetNumInstruments(); i++) if(sndFile.Instruments[i])
+ {
+ sndFile.Instruments[i]->nFadeOut = 256;
+ sndFile.Instruments[i]->nGlobalVol = 64;
+ sndFile.Instruments[i]->nPan = 128;
+ sndFile.Instruments[i]->dwFlags.reset(INS_SETPANNING);
+ sndFile.Instruments[i]->nMixPlug = 0;
+
+ sndFile.Instruments[i]->nVolSwing = 0;
+ sndFile.Instruments[i]->nPanSwing = 0;
+ sndFile.Instruments[i]->nCutSwing = 0;
+ sndFile.Instruments[i]->nResSwing = 0;
+ }
+
+ for(CHANNELINDEX chn = 0; chn < sndFile.GetNumChannels(); chn++)
+ {
+ sndFile.InitChannel(chn);
+ }
+
+ // reset samples
+ SampleEdit::ResetSamples(sndFile, SampleEdit::SmpResetCompo);
+
+ cs.Leave();
+ EndWaitCursor();
+
+ return true;
+}
+
+
+bool CModCleanupDlg::RemoveUnusedChannels()
+{
+ // Avoid M.K. modules to become xCHN modules if some channels are unused.
+ if(modDoc.GetModType() == MOD_TYPE_MOD && modDoc.GetNumChannels() == 4)
+ return false;
+
+ std::vector<bool> usedChannels;
+ modDoc.CheckUsedChannels(usedChannels, modDoc.GetNumChannels() - modDoc.GetSoundFile().GetModSpecifications().channelsMin);
+ return modDoc.RemoveChannels(usedChannels);
+}
+
+
+// Remove all patterns
+bool CModCleanupDlg::RemoveAllPatterns()
+{
+ CSoundFile &sndFile = modDoc.GetSoundFile();
+
+ if(sndFile.Patterns.Size() == 0) return false;
+ modDoc.GetPatternUndo().ClearUndo();
+ sndFile.Patterns.ResizeArray(0);
+ sndFile.SetCurrentOrder(0);
+ return true;
+}
+
+// Remove all orders
+bool CModCleanupDlg::RemoveAllOrders()
+{
+ CSoundFile &sndFile = modDoc.GetSoundFile();
+
+ sndFile.Order.Initialize();
+ sndFile.SetCurrentOrder(0);
+ return true;
+}
+
+// Remove all samples
+bool CModCleanupDlg::RemoveAllSamples()
+{
+ CSoundFile &sndFile = modDoc.GetSoundFile();
+
+ if (sndFile.GetNumSamples() == 0) return false;
+
+ std::vector<bool> keepSamples(sndFile.GetNumSamples() + 1, false);
+ sndFile.RemoveSelectedSamples(keepSamples);
+
+ SampleEdit::ResetSamples(sndFile, SampleEdit::SmpResetInit, 1, MAX_SAMPLES - 1);
+
+ return true;
+}
+
+// Remove all instruments
+bool CModCleanupDlg::RemoveAllInstruments()
+{
+ CSoundFile &sndFile = modDoc.GetSoundFile();
+
+ if(sndFile.GetNumInstruments() == 0) return false;
+
+ modDoc.ConvertInstrumentsToSamples();
+
+ for(INSTRUMENTINDEX i = 1; i <= sndFile.GetNumInstruments(); i++)
+ {
+ sndFile.DestroyInstrument(i, doNoDeleteAssociatedSamples);
+ }
+
+ sndFile.m_nInstruments = 0;
+ return true;
+}
+
+// Remove all plugins
+bool CModCleanupDlg::RemoveAllPlugins()
+{
+ std::vector<bool> keepMask(MAX_MIXPLUGINS, false);
+ modDoc.RemovePlugs(keepMask);
+ return true;
+}
+
+
+bool CModCleanupDlg::MergeSequences()
+{
+ return modDoc.GetSoundFile().Order.MergeSequences();
+}
+
+
+OPENMPT_NAMESPACE_END