diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/mptrack/Undo.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/mptrack/Undo.cpp')
-rw-r--r-- | Src/external_dependencies/openmpt-trunk/mptrack/Undo.cpp | 937 |
1 files changed, 937 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/mptrack/Undo.cpp b/Src/external_dependencies/openmpt-trunk/mptrack/Undo.cpp new file mode 100644 index 00000000..4f1ec01c --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/mptrack/Undo.cpp @@ -0,0 +1,937 @@ +/* + * Undo.cpp + * -------- + * Purpose: Editor undo buffer functionality. + * 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 "Undo.h" +#include "../common/mptStringBuffer.h" +#include "../tracklib/SampleEdit.h" +#include "../soundlib/modsmp_ctrl.h" + + +OPENMPT_NAMESPACE_BEGIN + + +///////////////////////////////////////////////////////////////////////////////////////// +// Pattern Undo Functions + + +// Remove all undo steps. +void CPatternUndo::ClearUndo() +{ + UndoBuffer.clear(); + RedoBuffer.clear(); +} + + +// Create undo point. +// Parameter list: +// - pattern: Pattern of which an undo step should be created from. +// - firstChn: first channel, 0-based. +// - firstRow: first row, 0-based. +// - numChns: width +// - numRows: height +// - description: Short description text of action for undo menu. +// - linkToPrevious: Don't create a separate undo step, but link this to the previous undo event. Use this for commands that modify several patterns at once. +// - storeChannelInfo: Also store current channel header information (pan / volume / etc. settings) and number of channels in this undo point. +bool CPatternUndo::PrepareUndo(PATTERNINDEX pattern, CHANNELINDEX firstChn, ROWINDEX firstRow, CHANNELINDEX numChns, ROWINDEX numRows, const char *description, bool linkToPrevious, bool storeChannelInfo) +{ + if(PrepareBuffer(UndoBuffer, pattern, firstChn, firstRow, numChns, numRows, description, linkToPrevious, storeChannelInfo)) + { + RedoBuffer.clear(); + return true; + } + return false; +} + + +bool CPatternUndo::PrepareChannelUndo(CHANNELINDEX firstChn, CHANNELINDEX numChns, const char *description) +{ + return PrepareUndo(PATTERNINDEX_INVALID, firstChn, 0, numChns, 0, description, false, true); +} + + +bool CPatternUndo::PrepareBuffer(undobuf_t &buffer, PATTERNINDEX pattern, CHANNELINDEX firstChn, ROWINDEX firstRow, CHANNELINDEX numChns, ROWINDEX numRows, const char *description, bool linkToPrevious, bool storeChannelInfo) const +{ + const CSoundFile &sndFile = modDoc.GetSoundFile(); + const bool onlyChannelInfo = storeChannelInfo && numRows < 1; + + if(storeChannelInfo && pattern != PATTERNINDEX_INVALID && firstChn == 0 && numChns != sndFile.GetNumChannels()) + { + numChns = sndFile.GetNumChannels(); + } + + ROWINDEX patRows = 0; + if(sndFile.Patterns.IsValidPat(pattern)) + { + patRows = sndFile.Patterns[pattern].GetNumRows(); + if((firstRow >= patRows) || (firstChn >= sndFile.GetNumChannels())) + return false; + if(numChns < 1 || numRows < 1) + return false; + if(firstRow + numRows >= patRows) + numRows = patRows - firstRow; + if(firstChn + numChns >= sndFile.GetNumChannels()) + numChns = sndFile.GetNumChannels() - firstChn; + } else if(!onlyChannelInfo) + { + return false; + } + + // Remove an undo step if there are too many. + if(buffer.size() >= MAX_UNDO_LEVEL) + { + buffer.erase(buffer.begin(), buffer.begin() + (buffer.size() - MAX_UNDO_LEVEL + 1)); + } + + UndoInfo undo; + undo.pattern = pattern; + undo.numPatternRows = patRows; + undo.firstChannel = firstChn; + undo.firstRow = firstRow; + undo.numChannels = numChns; + undo.numRows = numRows; + undo.linkToPrevious = linkToPrevious; + undo.description = description; + + if(!onlyChannelInfo) + { + try + { + undo.content.resize(numRows * numChns); + } catch(mpt::out_of_memory e) + { + mpt::delete_out_of_memory(e); + return false; + } + const ModCommand *pPattern = sndFile.Patterns[pattern].GetpModCommand(firstRow, firstChn); + auto pUndoData = undo.content.begin(); + for(ROWINDEX iy = 0; iy < numRows; iy++) + { + std::copy(pPattern, pPattern + numChns, pUndoData); + pUndoData += numChns; + pPattern += sndFile.GetNumChannels(); + } + } + + if(storeChannelInfo) + { + undo.channelInfo.assign(std::begin(sndFile.ChnSettings) + firstChn, std::begin(sndFile.ChnSettings) + firstChn + numChns); + } + + buffer.push_back(std::move(undo)); + + if(!linkToPrevious) + modDoc.UpdateAllViews(nullptr, UpdateHint().Undo()); + return true; +} + + +// Restore an undo point. Returns which pattern has been modified. +PATTERNINDEX CPatternUndo::Undo() +{ + return Undo(UndoBuffer, RedoBuffer, false); +} + + +// Restore an undo point. Returns which pattern has been modified. +PATTERNINDEX CPatternUndo::Redo() +{ + return Undo(RedoBuffer, UndoBuffer, false); +} + + +// Restore an undo point. Returns which pattern has been modified. +// linkedFromPrevious is true if a connected undo event is going to be deleted (can only be called internally). +PATTERNINDEX CPatternUndo::Undo(undobuf_t &fromBuf, undobuf_t &toBuf, bool linkedFromPrevious) +{ + CSoundFile &sndFile = modDoc.GetSoundFile(); + + bool linkToPrevious = false; + + if(fromBuf.empty()) + return PATTERNINDEX_INVALID; + + // Select most recent undo slot + const UndoInfo &undo = fromBuf.back(); + const bool onlyChannelSettings = undo.OnlyChannelSettings(); + const bool deletePattern = (undo.numPatternRows == DELETE_PATTERN) && !onlyChannelSettings; + + // Add this action to redo buffer if the pattern exists; otherwise add a special deletion redo step later + const bool patternExists = sndFile.Patterns.IsValidPat(undo.pattern); + if(patternExists || onlyChannelSettings) + PrepareBuffer(toBuf, undo.pattern, undo.firstChannel, undo.firstRow, undo.numChannels, undo.numRows, undo.description, linkedFromPrevious, !undo.channelInfo.empty()); + + const bool modifyChannels = !undo.channelInfo.empty(); + const CHANNELINDEX updateChannel = (undo.numChannels == 1) ? undo.firstChannel : CHANNELINDEX_INVALID; + if(modifyChannels) + { + const bool modifyChannelCount = + (undo.pattern != PATTERNINDEX_INVALID && undo.channelInfo.size() != sndFile.GetNumChannels()) + || (undo.pattern == PATTERNINDEX_INVALID && (undo.firstChannel + undo.channelInfo.size()) > sndFile.GetNumChannels()); + if(modifyChannelCount) + { + // Add or remove channels + std::vector<CHANNELINDEX> channels(undo.channelInfo.size(), CHANNELINDEX_INVALID); + const CHANNELINDEX copyCount = std::min(sndFile.GetNumChannels(), static_cast<CHANNELINDEX>(undo.channelInfo.size())); + std::iota(channels.begin(), channels.begin() + copyCount, CHANNELINDEX(0)); + modDoc.ReArrangeChannels(channels, false); + } + if(undo.firstChannel + undo.channelInfo.size() <= sndFile.GetNumChannels()) + { + std::move(undo.channelInfo.cbegin(), undo.channelInfo.cend(), std::begin(sndFile.ChnSettings) + undo.firstChannel); + } + + // Channel mute status might have changed... + for(CHANNELINDEX i = undo.firstChannel; i < sndFile.GetNumChannels(); i++) + { + modDoc.UpdateChannelMuteStatus(i); + } + } + + PATTERNINDEX pat = undo.pattern; + if(deletePattern) + { + sndFile.Patterns.Remove(pat); + } else if(undo.firstChannel + undo.numChannels <= sndFile.GetNumChannels() && !onlyChannelSettings) + { + if(!patternExists) + { + if(!sndFile.Patterns.Insert(pat, undo.numPatternRows)) + { + fromBuf.pop_back(); + return PATTERNINDEX_INVALID; + } + } else if(sndFile.Patterns[pat].GetNumRows() != undo.numPatternRows) + { + sndFile.Patterns[pat].Resize(undo.numPatternRows); + } + + linkToPrevious = undo.linkToPrevious; + auto pUndoData = undo.content.cbegin(); + CPattern &pattern = sndFile.Patterns[pat]; + ModCommand *m = pattern.GetpModCommand(undo.firstRow, undo.firstChannel); + const ROWINDEX numRows = std::min(undo.numRows, pattern.GetNumRows()); + for(ROWINDEX iy = 0; iy < numRows; iy++) + { + std::move(pUndoData, pUndoData + undo.numChannels, m); + m += sndFile.GetNumChannels(); + pUndoData += undo.numChannels; + } + } + + if(!patternExists && !onlyChannelSettings) + { + // Redo a deletion + auto &redo = fromBuf.back(); + redo.content.clear(); + redo.numPatternRows = DELETE_PATTERN; + toBuf.push_back(std::move(redo)); + } + fromBuf.pop_back(); + + if(patternExists != sndFile.Patterns.IsValidPat(pat)) + { + modDoc.UpdateAllViews(nullptr, PatternHint(pat).Names().Undo()); + modDoc.UpdateAllViews(nullptr, SequenceHint().Data()); // Pattern color will change in sequence + } else + { + modDoc.UpdateAllViews(nullptr, UpdateHint().Undo()); + } + if(modifyChannels) + modDoc.UpdateAllViews(nullptr, GeneralHint(updateChannel).Channels()); + modDoc.SetModified(); + + if(linkToPrevious) + { + pat = Undo(fromBuf, toBuf, true); + } + + return pat; +} + + +// Public helper function to remove the most recent undo point. +void CPatternUndo::RemoveLastUndoStep() +{ + if(UndoBuffer.empty()) + return; + + UndoBuffer.pop_back(); + modDoc.UpdateAllViews(nullptr, UpdateHint().Undo()); +} + + +CString CPatternUndo::GetName(const undobuf_t &buffer) +{ + if(buffer.empty()) + return CString(); + + const UndoInfo &info = buffer.back(); + CString desc = mpt::ToCString(mpt::Charset::Locale, info.description); + if(info.linkToPrevious) + desc += _T(" (Multiple Patterns)"); + else if(info.OnlyChannelSettings() && info.numChannels > 1) + desc += _T(" (Multiple Channels)"); + else if(info.OnlyChannelSettings()) + desc += MPT_CFORMAT(" (Channel {})")(info.firstChannel + 1); + else + desc += MPT_CFORMAT(" (Pat {} Row {} Chn {})")(info.pattern, info.firstRow, info.firstChannel + 1); + return desc; +} + + +void CPatternUndo::RearrangePatterns(undobuf_t &buffer, const std::vector<PATTERNINDEX> &newIndex) +{ + for(auto &step : buffer) + { + if(step.pattern < newIndex.size()) + step.pattern = newIndex[step.pattern]; + } +} + + +void CPatternUndo::RearrangePatterns(const std::vector<PATTERNINDEX> &newIndex) +{ + RearrangePatterns(UndoBuffer, newIndex); + RearrangePatterns(RedoBuffer, newIndex); +} + + +///////////////////////////////////////////////////////////////////////////////////////// +// Sample Undo Functions + + +// Remove all undo steps for all samples. +void CSampleUndo::ClearUndo() +{ + for(SAMPLEINDEX smp = 1; smp <= MAX_SAMPLES; smp++) + { + ClearUndo(UndoBuffer, smp); + ClearUndo(RedoBuffer, smp); + } + UndoBuffer.clear(); + RedoBuffer.clear(); +} + + +// Remove all undo steps of a given sample. +void CSampleUndo::ClearUndo(undobuf_t &buffer, const SAMPLEINDEX smp) +{ + if(!SampleBufferExists(buffer, smp)) return; + + while(!buffer[smp - 1].empty()) + { + DeleteStep(buffer, smp, 0); + } +} + + +// Create undo point for given sample. +// The main program has to tell what kind of changes are going to be made to the sample. +// That way, a lot of RAM can be saved, because some actions don't even require an undo sample buffer. +bool CSampleUndo::PrepareUndo(const SAMPLEINDEX smp, sampleUndoTypes changeType, const char *description, SmpLength changeStart, SmpLength changeEnd) +{ + if(PrepareBuffer(UndoBuffer, smp, changeType, description, changeStart, changeEnd)) + { + ClearUndo(RedoBuffer, smp); + return true; + } + return false; +} + + +bool CSampleUndo::PrepareBuffer(undobuf_t &buffer, const SAMPLEINDEX smp, sampleUndoTypes changeType, const char *description, SmpLength changeStart, SmpLength changeEnd) +{ + if(smp == 0 || smp >= MAX_SAMPLES) return false; + if(!TrackerSettings::Instance().m_SampleUndoBufferSize.Get().GetSizeInBytes()) + { + // Undo/Redo is disabled + return false; + } + if(smp > buffer.size()) + { + buffer.resize(smp); + } + + // Remove an undo step if there are too many. + while(buffer[smp - 1].size() >= MAX_UNDO_LEVEL) + { + DeleteStep(buffer, smp, 0); + } + + // Create new undo slot + UndoInfo undo; + + const CSoundFile &sndFile = modDoc.GetSoundFile(); + const ModSample &oldSample = sndFile.GetSample(smp); + + // Save old sample header + undo.OldSample = oldSample; + undo.oldName = sndFile.m_szNames[smp]; + undo.changeType = changeType; + undo.description = description; + + if(changeType == sundo_replace) + { + // ensure that size information is correct here. + changeStart = 0; + changeEnd = oldSample.nLength; + } else if(changeType == sundo_none) + { + // we do nothing... + changeStart = changeEnd = 0; + } + + if(changeStart > oldSample.nLength || changeStart > changeEnd) + { + // Something is surely screwed up. + MPT_ASSERT(false); + return false; + } + + // Restrict amount of memory that's being used + RestrictBufferSize(); + + undo.changeStart = changeStart; + undo.changeEnd = changeEnd; + undo.samplePtr = nullptr; + + switch(changeType) + { + case sundo_none: // we are done, no sample changes here. + case sundo_invert: // no action necessary, since those effects can be applied again to be undone. + case sundo_reverse: // ditto + case sundo_unsign: // ditto + case sundo_insert: // no action necessary, we already have stored the variables that are necessary. + break; + + case sundo_update: + case sundo_delete: + case sundo_replace: + if(oldSample.HasSampleData()) + { + const uint8 bytesPerSample = oldSample.GetBytesPerSample(); + const SmpLength changeLen = changeEnd - changeStart; + + undo.samplePtr = ModSample::AllocateSample(changeLen, bytesPerSample); + if(undo.samplePtr == nullptr) return false; + memcpy(undo.samplePtr, oldSample.sampleb() + changeStart * bytesPerSample, changeLen * bytesPerSample); + +#ifdef MPT_ALL_LOGGING + const size_t nSize = (GetBufferCapacity(UndoBuffer) + GetBufferCapacity(RedoBuffer) + changeLen * bytesPerSample) >> 10; + MPT_LOG_GLOBAL(LogDebug, "Undo", MPT_UFORMAT("Sample undo/redo buffer size is now {}.{} MB")(nSize >> 10, (nSize & 1023) * 100 / 1024)); +#endif + + } + break; + + default: + MPT_ASSERT(false); // whoops, what's this? someone forgot to implement it, some code is obviously missing here! + return false; + } + + buffer[smp - 1].push_back(std::move(undo)); + + modDoc.UpdateAllViews(nullptr, UpdateHint().Undo()); + + return true; +} + + +// Restore undo point for given sample +bool CSampleUndo::Undo(const SAMPLEINDEX smp) +{ + return Undo(UndoBuffer, RedoBuffer, smp); +} + + +// Restore redo point for given sample +bool CSampleUndo::Redo(const SAMPLEINDEX smp) +{ + return Undo(RedoBuffer, UndoBuffer, smp); +} + + +// Restore undo/redo point for given sample +bool CSampleUndo::Undo(undobuf_t &fromBuf, undobuf_t &toBuf, const SAMPLEINDEX smp) +{ + if(!SampleBufferExists(fromBuf, smp) || fromBuf[smp - 1].empty()) return false; + + CSoundFile &sndFile = modDoc.GetSoundFile(); + + // Select most recent undo slot and temporarily remove it from the buffer so that it won't get deleted by possible buffer size restrictions in PrepareBuffer() + UndoInfo undo = fromBuf[smp - 1].back(); + fromBuf[smp - 1].pop_back(); + + // When turning an undo point into a redo point (and vice versa), some action types need to be adjusted. + sampleUndoTypes redoType = undo.changeType; + if(redoType == sundo_delete) + redoType = sundo_insert; + else if(redoType == sundo_insert) + redoType = sundo_delete; + PrepareBuffer(toBuf, smp, redoType, undo.description, undo.changeStart, undo.changeEnd); + + ModSample &sample = sndFile.GetSample(smp); + std::byte *pCurrentSample = mpt::void_cast<std::byte*>(sample.samplev()); + int8 *pNewSample = nullptr; // a new sample is possibly going to be allocated, depending on what's going to be undone. + bool keepOnDisk = sample.uFlags[SMP_KEEPONDISK]; + bool replace = false; + + uint8 bytesPerSample = undo.OldSample.GetBytesPerSample(); + SmpLength changeLen = undo.changeEnd - undo.changeStart; + + switch(undo.changeType) + { + case sundo_none: + break; + + case sundo_invert: + // invert again + SampleEdit::InvertSample(sample, undo.changeStart, undo.changeEnd, sndFile); + break; + + case sundo_reverse: + // reverse again + SampleEdit::ReverseSample(sample, undo.changeStart, undo.changeEnd, sndFile); + break; + + case sundo_unsign: + // unsign again + SampleEdit::UnsignSample(sample, undo.changeStart, undo.changeEnd, sndFile); + break; + + case sundo_insert: + // delete inserted data + MPT_ASSERT(changeLen == sample.nLength - undo.OldSample.nLength); + if(undo.OldSample.nLength > 0) + { + memcpy(pCurrentSample + undo.changeStart * bytesPerSample, pCurrentSample + undo.changeEnd * bytesPerSample, (sample.nLength - undo.changeEnd) * bytesPerSample); + // also clean the sample end + memset(pCurrentSample + undo.OldSample.nLength * bytesPerSample, 0, (sample.nLength - undo.OldSample.nLength) * bytesPerSample); + } else + { + replace = true; + } + break; + + case sundo_update: + // simply replace what has been updated. + if(sample.nLength < undo.changeEnd) return false; + memcpy(pCurrentSample + undo.changeStart * bytesPerSample, undo.samplePtr, changeLen * bytesPerSample); + break; + + case sundo_delete: + // insert deleted data + pNewSample = static_cast<int8 *>(ModSample::AllocateSample(undo.OldSample.nLength, bytesPerSample)); + if(pNewSample == nullptr) return false; + replace = true; + memcpy(pNewSample, pCurrentSample, undo.changeStart * bytesPerSample); + memcpy(pNewSample + undo.changeStart * bytesPerSample, undo.samplePtr, changeLen * bytesPerSample); + memcpy(pNewSample + undo.changeEnd * bytesPerSample, pCurrentSample + undo.changeStart * bytesPerSample, (undo.OldSample.nLength - undo.changeEnd) * bytesPerSample); + break; + + case sundo_replace: + // simply exchange sample pointer + pNewSample = static_cast<int8 *>(undo.samplePtr); + undo.samplePtr = nullptr; // prevent sample from being deleted + replace = true; + break; + + default: + MPT_ASSERT(false); // whoops, what's this? someone forgot to implement it, some code is obviously missing here! + return false; + } + + // Restore old sample header + sample = undo.OldSample; + sample.pData.pSample = mpt::void_cast<void*>(pCurrentSample); // select the "correct" old sample + sndFile.m_szNames[smp] = undo.oldName; + + if(replace) + { + ctrlSmp::ReplaceSample(sample, pNewSample, undo.OldSample.nLength, sndFile); + } + sample.PrecomputeLoops(sndFile, true); + + if(undo.changeType != sundo_none) + { + sample.uFlags.set(SMP_MODIFIED); + } + if(!keepOnDisk) + { + // Never re-enable the keep on disk flag after it was disabled. + // This can lead to quite some dangerous situations when replacing samples. + sample.uFlags.reset(SMP_KEEPONDISK); + } + + fromBuf[smp - 1].push_back(std::move(undo)); + DeleteStep(fromBuf, smp, fromBuf[smp - 1].size() - 1); + + modDoc.UpdateAllViews(nullptr, UpdateHint().Undo()); + modDoc.SetModified(); + + return true; +} + + +// Delete a given undo / redo step of a sample. +void CSampleUndo::DeleteStep(undobuf_t &buffer, const SAMPLEINDEX smp, const size_t step) +{ + if(!SampleBufferExists(buffer, smp) || step >= buffer[smp - 1].size()) return; + ModSample::FreeSample(buffer[smp - 1][step].samplePtr); + buffer[smp - 1].erase(buffer[smp - 1].begin() + step); +} + + +// Public helper function to remove the most recent undo point. +void CSampleUndo::RemoveLastUndoStep(const SAMPLEINDEX smp) +{ + if(!CanUndo(smp)) + return; + + DeleteStep(UndoBuffer, smp, UndoBuffer[smp - 1].size() - 1); + modDoc.UpdateAllViews(nullptr, UpdateHint().Undo()); +} + + +// Restrict undo buffer size so it won't grow too large. +// This is done in FIFO style, equally distributed over all sample slots (very simple). +void CSampleUndo::RestrictBufferSize() +{ + size_t capacity = GetBufferCapacity(UndoBuffer) + GetBufferCapacity(RedoBuffer); + while(capacity > TrackerSettings::Instance().m_SampleUndoBufferSize.Get().GetSizeInBytes()) + { + RestrictBufferSize(UndoBuffer, capacity); + RestrictBufferSize(RedoBuffer, capacity); + } +} + + +void CSampleUndo::RestrictBufferSize(undobuf_t &buffer, size_t &capacity) +{ + for(SAMPLEINDEX smp = 1; smp <= buffer.size(); smp++) + { + if(capacity <= TrackerSettings::Instance().m_SampleUndoBufferSize.Get().GetSizeInBytes()) return; + for(size_t i = 0; i < buffer[smp - 1].size(); i++) + { + if(buffer[smp - 1][i].samplePtr != nullptr) + { + capacity -= (buffer[smp - 1][i].changeEnd - buffer[smp - 1][i].changeStart) * buffer[smp - 1][i].OldSample.GetBytesPerSample(); + for(size_t j = 0; j <= i; j++) + { + DeleteStep(buffer, smp, 0); + } + // Try to evenly spread out the restriction, i.e. move on to other samples before deleting another step for this sample. + break; + } + } + } +} + + +// Update undo buffer when using rearrange sample functionality. +// newIndex contains one new index for each old index. newIndex[1] represents the first sample. +void CSampleUndo::RearrangeSamples(undobuf_t &buffer, const std::vector<SAMPLEINDEX> &newIndex) +{ + undobuf_t newBuf(modDoc.GetNumSamples()); + + const SAMPLEINDEX newSize = static_cast<SAMPLEINDEX>(newIndex.size()); + const SAMPLEINDEX oldSize = static_cast<SAMPLEINDEX>(buffer.size()); + for(SAMPLEINDEX smp = 1; smp <= oldSize; smp++) + { + MPT_ASSERT(smp >= newSize || newIndex[smp] <= modDoc.GetNumSamples()); + if(smp < newSize && newIndex[smp] > 0 && newIndex[smp] <= modDoc.GetNumSamples()) + { + newBuf[newIndex[smp] - 1] = buffer[smp - 1]; + } else + { + ClearUndo(smp); + } + } +#ifdef _DEBUG + for(size_t i = 0; i < oldSize; i++) + { + if(i + 1 < newIndex.size() && newIndex[i + 1] != 0) + MPT_ASSERT(newBuf[newIndex[i + 1] - 1].size() == buffer[i].size()); + else + MPT_ASSERT(buffer[i].empty()); + } +#endif + buffer = newBuf; +} + + +// Return total amount of bytes used by the sample undo buffer. +size_t CSampleUndo::GetBufferCapacity(const undobuf_t &buffer) const +{ + size_t sum = 0; + for(auto &smp : buffer) + { + for(auto &step : smp) + { + if(step.samplePtr != nullptr) + { + sum += (step.changeEnd - step.changeStart) * step.OldSample.GetBytesPerSample(); + } + } + } + return sum; +} + + +// Ensure that the undo buffer is big enough for a given sample number +bool CSampleUndo::SampleBufferExists(const undobuf_t &buffer, const SAMPLEINDEX smp) const +{ + if(smp == 0 || smp >= MAX_SAMPLES) return false; + if(smp <= buffer.size()) return true; + return false; +} + +// Get name of next undo item +const char *CSampleUndo::GetUndoName(const SAMPLEINDEX smp) const +{ + if(!CanUndo(smp)) + { + return ""; + } + return UndoBuffer[smp - 1].back().description; +} + + +// Get name of next redo item +const char *CSampleUndo::GetRedoName(const SAMPLEINDEX smp) const +{ + if(!CanRedo(smp)) + { + return ""; + } + return RedoBuffer[smp - 1].back().description; +} + + +///////////////////////////////////////////////////////////////////////////////////////// +// Instrument Undo Functions + + +// Remove all undo steps for all instruments. +void CInstrumentUndo::ClearUndo() +{ + UndoBuffer.clear(); + RedoBuffer.clear(); +} + + +// Remove all undo steps of a given instrument. +void CInstrumentUndo::ClearUndo(undobuf_t &buffer, const INSTRUMENTINDEX ins) +{ + if(!InstrumentBufferExists(buffer, ins)) return; + buffer[ins - 1].clear(); +} + + +// Create undo point for given Instrument. +// The main program has to tell what kind of changes are going to be made to the Instrument. +// That way, a lot of RAM can be saved, because some actions don't even require an undo Instrument buffer. +bool CInstrumentUndo::PrepareUndo(const INSTRUMENTINDEX ins, const char *description, EnvelopeType envType) +{ + if(PrepareBuffer(UndoBuffer, ins, description, envType)) + { + ClearUndo(RedoBuffer, ins); + return true; + } + return false; +} + + +bool CInstrumentUndo::PrepareBuffer(undobuf_t &buffer, const INSTRUMENTINDEX ins, const char *description, EnvelopeType envType) +{ + if(ins == 0 || ins >= MAX_INSTRUMENTS || modDoc.GetSoundFile().Instruments[ins] == nullptr) return false; + if(ins > buffer.size()) + { + buffer.resize(ins); + } + auto &insBuffer = buffer[ins - 1]; + + // Remove undo steps if there are too many. + if(insBuffer.size() >= MAX_UNDO_LEVEL) + { + insBuffer.erase(insBuffer.begin(), insBuffer.begin() + (insBuffer.size() - MAX_UNDO_LEVEL + 1)); + } + + // Create new undo slot + UndoInfo undo; + + const CSoundFile &sndFile = modDoc.GetSoundFile(); + undo.description = description; + undo.editedEnvelope = envType; + if(envType < ENV_MAXTYPES) + { + undo.instr.GetEnvelope(envType) = sndFile.Instruments[ins]->GetEnvelope(envType); + } else + { + undo.instr = *sndFile.Instruments[ins]; + } + // cppcheck false-positive + // cppcheck-suppress uninitStructMember + insBuffer.push_back(std::move(undo)); + + modDoc.UpdateAllViews(nullptr, UpdateHint().Undo()); + + return true; +} + + +// Restore undo point for given Instrument +bool CInstrumentUndo::Undo(const INSTRUMENTINDEX ins) +{ + return Undo(UndoBuffer, RedoBuffer, ins); +} + + +// Restore redo point for given Instrument +bool CInstrumentUndo::Redo(const INSTRUMENTINDEX ins) +{ + return Undo(RedoBuffer, UndoBuffer, ins); +} + + +// Restore undo/redo point for given Instrument +bool CInstrumentUndo::Undo(undobuf_t &fromBuf, undobuf_t &toBuf, const INSTRUMENTINDEX ins) +{ + CSoundFile &sndFile = modDoc.GetSoundFile(); + if(sndFile.Instruments[ins] == nullptr || !InstrumentBufferExists(fromBuf, ins) || fromBuf[ins - 1].empty()) return false; + + // Select most recent undo slot + const UndoInfo &undo = fromBuf[ins - 1].back(); + + PrepareBuffer(toBuf, ins, undo.description, undo.editedEnvelope); + + // When turning an undo point into a redo point (and vice versa), some action types need to be adjusted. + ModInstrument *instr = sndFile.Instruments[ins]; + if(undo.editedEnvelope < ENV_MAXTYPES) + { + instr->GetEnvelope(undo.editedEnvelope) = undo.instr.GetEnvelope(undo.editedEnvelope); + } else + { + *instr = undo.instr; + } + + DeleteStep(fromBuf, ins, fromBuf[ins - 1].size() - 1); + + modDoc.UpdateAllViews(nullptr, UpdateHint().Undo()); + modDoc.SetModified(); + + return true; +} + + +// Delete a given undo / redo step of a Instrument. +void CInstrumentUndo::DeleteStep(undobuf_t &buffer, const INSTRUMENTINDEX ins, const size_t step) +{ + if(!InstrumentBufferExists(buffer, ins) || step >= buffer[ins - 1].size()) return; + buffer[ins - 1].erase(buffer[ins - 1].begin() + step); +} + + +// Public helper function to remove the most recent undo point. +void CInstrumentUndo::RemoveLastUndoStep(const INSTRUMENTINDEX ins) +{ + if(!CanUndo(ins)) return; + DeleteStep(UndoBuffer, ins, UndoBuffer[ins - 1].size() - 1); +} + + +// Update undo buffer when using rearrange instruments functionality. +// newIndex contains one new index for each old index. newIndex[1] represents the first instrument. +void CInstrumentUndo::RearrangeInstruments(undobuf_t &buffer, const std::vector<INSTRUMENTINDEX> &newIndex) +{ + undobuf_t newBuf(modDoc.GetNumInstruments()); + + const INSTRUMENTINDEX newSize = static_cast<INSTRUMENTINDEX>(newIndex.size()); + const INSTRUMENTINDEX oldSize = static_cast<INSTRUMENTINDEX>(buffer.size()); + for(INSTRUMENTINDEX ins = 1; ins <= oldSize; ins++) + { + MPT_ASSERT(ins >= newSize || newIndex[ins] <= modDoc.GetNumInstruments()); + if(ins < newSize && newIndex[ins] > 0 && newIndex[ins] <= modDoc.GetNumInstruments()) + { + newBuf[newIndex[ins] - 1] = buffer[ins - 1]; + } else + { + ClearUndo(ins); + } + } +#ifdef _DEBUG + for(size_t i = 0; i < oldSize; i++) + { + if(i + 1 < newIndex.size() && newIndex[i + 1] != 0) + MPT_ASSERT(newBuf[newIndex[i + 1] - 1].size() == buffer[i].size()); + else + MPT_ASSERT(buffer[i].empty()); + } +#endif + buffer = newBuf; +} + + +// Update undo buffer when using rearrange samples functionality. +// newIndex contains one new index for each old index. newIndex[1] represents the first sample. +void CInstrumentUndo::RearrangeSamples(undobuf_t &buffer, const INSTRUMENTINDEX ins, std::vector<SAMPLEINDEX> &newIndex) +{ + const CSoundFile &sndFile = modDoc.GetSoundFile(); + if(sndFile.Instruments[ins] == nullptr || !InstrumentBufferExists(buffer, ins) || buffer[ins - 1].empty()) return; + + for(auto &i : buffer[ins - 1]) if(i.editedEnvelope >= ENV_MAXTYPES) + { + for(auto &sample : i.instr.Keyboard) + { + if(sample < newIndex.size()) + sample = newIndex[sample]; + else + sample = 0; + } + } +} + + +// Ensure that the undo buffer is big enough for a given Instrument number +bool CInstrumentUndo::InstrumentBufferExists(const undobuf_t &buffer, const INSTRUMENTINDEX ins) const +{ + if(ins == 0 || ins >= MAX_INSTRUMENTS) return false; + if(ins <= buffer.size()) return true; + return false; +} + + +// Get name of next undo item +const char *CInstrumentUndo::GetUndoName(const INSTRUMENTINDEX ins) const +{ + if(!CanUndo(ins)) + { + return ""; + } + return UndoBuffer[ins - 1].back().description; +} + + +// Get name of next redo item +const char *CInstrumentUndo::GetRedoName(const INSTRUMENTINDEX ins) const +{ + if(!CanRedo(ins)) + { + return ""; + } + return RedoBuffer[ins - 1].back().description; +} + + +OPENMPT_NAMESPACE_END |