winamp/Src/external_dependencies/openmpt-trunk/mptrack/CleanupSong.cpp
2024-09-24 14:54:57 +02:00

977 lines
27 KiB
C++

/*
* 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