/* * 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(lpszText); return TRUE; } /////////////////////////////////////////////////////////////////////// // Actual cleanup implementations bool CModCleanupDlg::RemoveDuplicatePatterns() { CSoundFile &sndFile = modDoc.GetSoundFile(); const PATTERNINDEX numPatterns = sndFile.Patterns.Size(); std::vector 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 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 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(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 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 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 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 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 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 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 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 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 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 keepMask(MAX_MIXPLUGINS, false); modDoc.RemovePlugs(keepMask); return true; } bool CModCleanupDlg::MergeSequences() { return modDoc.GetSoundFile().Order.MergeSequences(); } OPENMPT_NAMESPACE_END