Undo.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  1. /*
  2. * Undo.cpp
  3. * --------
  4. * Purpose: Editor undo buffer functionality.
  5. * Notes : (currently none)
  6. * Authors: Olivier Lapicque
  7. * OpenMPT Devs
  8. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  9. */
  10. #include "stdafx.h"
  11. #include "Moddoc.h"
  12. #include "Mainfrm.h"
  13. #include "Undo.h"
  14. #include "../common/mptStringBuffer.h"
  15. #include "../tracklib/SampleEdit.h"
  16. #include "../soundlib/modsmp_ctrl.h"
  17. OPENMPT_NAMESPACE_BEGIN
  18. /////////////////////////////////////////////////////////////////////////////////////////
  19. // Pattern Undo Functions
  20. // Remove all undo steps.
  21. void CPatternUndo::ClearUndo()
  22. {
  23. UndoBuffer.clear();
  24. RedoBuffer.clear();
  25. }
  26. // Create undo point.
  27. // Parameter list:
  28. // - pattern: Pattern of which an undo step should be created from.
  29. // - firstChn: first channel, 0-based.
  30. // - firstRow: first row, 0-based.
  31. // - numChns: width
  32. // - numRows: height
  33. // - description: Short description text of action for undo menu.
  34. // - 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.
  35. // - storeChannelInfo: Also store current channel header information (pan / volume / etc. settings) and number of channels in this undo point.
  36. bool CPatternUndo::PrepareUndo(PATTERNINDEX pattern, CHANNELINDEX firstChn, ROWINDEX firstRow, CHANNELINDEX numChns, ROWINDEX numRows, const char *description, bool linkToPrevious, bool storeChannelInfo)
  37. {
  38. if(PrepareBuffer(UndoBuffer, pattern, firstChn, firstRow, numChns, numRows, description, linkToPrevious, storeChannelInfo))
  39. {
  40. RedoBuffer.clear();
  41. return true;
  42. }
  43. return false;
  44. }
  45. bool CPatternUndo::PrepareChannelUndo(CHANNELINDEX firstChn, CHANNELINDEX numChns, const char *description)
  46. {
  47. return PrepareUndo(PATTERNINDEX_INVALID, firstChn, 0, numChns, 0, description, false, true);
  48. }
  49. bool CPatternUndo::PrepareBuffer(undobuf_t &buffer, PATTERNINDEX pattern, CHANNELINDEX firstChn, ROWINDEX firstRow, CHANNELINDEX numChns, ROWINDEX numRows, const char *description, bool linkToPrevious, bool storeChannelInfo) const
  50. {
  51. const CSoundFile &sndFile = modDoc.GetSoundFile();
  52. const bool onlyChannelInfo = storeChannelInfo && numRows < 1;
  53. if(storeChannelInfo && pattern != PATTERNINDEX_INVALID && firstChn == 0 && numChns != sndFile.GetNumChannels())
  54. {
  55. numChns = sndFile.GetNumChannels();
  56. }
  57. ROWINDEX patRows = 0;
  58. if(sndFile.Patterns.IsValidPat(pattern))
  59. {
  60. patRows = sndFile.Patterns[pattern].GetNumRows();
  61. if((firstRow >= patRows) || (firstChn >= sndFile.GetNumChannels()))
  62. return false;
  63. if(numChns < 1 || numRows < 1)
  64. return false;
  65. if(firstRow + numRows >= patRows)
  66. numRows = patRows - firstRow;
  67. if(firstChn + numChns >= sndFile.GetNumChannels())
  68. numChns = sndFile.GetNumChannels() - firstChn;
  69. } else if(!onlyChannelInfo)
  70. {
  71. return false;
  72. }
  73. // Remove an undo step if there are too many.
  74. if(buffer.size() >= MAX_UNDO_LEVEL)
  75. {
  76. buffer.erase(buffer.begin(), buffer.begin() + (buffer.size() - MAX_UNDO_LEVEL + 1));
  77. }
  78. UndoInfo undo;
  79. undo.pattern = pattern;
  80. undo.numPatternRows = patRows;
  81. undo.firstChannel = firstChn;
  82. undo.firstRow = firstRow;
  83. undo.numChannels = numChns;
  84. undo.numRows = numRows;
  85. undo.linkToPrevious = linkToPrevious;
  86. undo.description = description;
  87. if(!onlyChannelInfo)
  88. {
  89. try
  90. {
  91. undo.content.resize(numRows * numChns);
  92. } catch(mpt::out_of_memory e)
  93. {
  94. mpt::delete_out_of_memory(e);
  95. return false;
  96. }
  97. const ModCommand *pPattern = sndFile.Patterns[pattern].GetpModCommand(firstRow, firstChn);
  98. auto pUndoData = undo.content.begin();
  99. for(ROWINDEX iy = 0; iy < numRows; iy++)
  100. {
  101. std::copy(pPattern, pPattern + numChns, pUndoData);
  102. pUndoData += numChns;
  103. pPattern += sndFile.GetNumChannels();
  104. }
  105. }
  106. if(storeChannelInfo)
  107. {
  108. undo.channelInfo.assign(std::begin(sndFile.ChnSettings) + firstChn, std::begin(sndFile.ChnSettings) + firstChn + numChns);
  109. }
  110. buffer.push_back(std::move(undo));
  111. if(!linkToPrevious)
  112. modDoc.UpdateAllViews(nullptr, UpdateHint().Undo());
  113. return true;
  114. }
  115. // Restore an undo point. Returns which pattern has been modified.
  116. PATTERNINDEX CPatternUndo::Undo()
  117. {
  118. return Undo(UndoBuffer, RedoBuffer, false);
  119. }
  120. // Restore an undo point. Returns which pattern has been modified.
  121. PATTERNINDEX CPatternUndo::Redo()
  122. {
  123. return Undo(RedoBuffer, UndoBuffer, false);
  124. }
  125. // Restore an undo point. Returns which pattern has been modified.
  126. // linkedFromPrevious is true if a connected undo event is going to be deleted (can only be called internally).
  127. PATTERNINDEX CPatternUndo::Undo(undobuf_t &fromBuf, undobuf_t &toBuf, bool linkedFromPrevious)
  128. {
  129. CSoundFile &sndFile = modDoc.GetSoundFile();
  130. bool linkToPrevious = false;
  131. if(fromBuf.empty())
  132. return PATTERNINDEX_INVALID;
  133. // Select most recent undo slot
  134. const UndoInfo &undo = fromBuf.back();
  135. const bool onlyChannelSettings = undo.OnlyChannelSettings();
  136. const bool deletePattern = (undo.numPatternRows == DELETE_PATTERN) && !onlyChannelSettings;
  137. // Add this action to redo buffer if the pattern exists; otherwise add a special deletion redo step later
  138. const bool patternExists = sndFile.Patterns.IsValidPat(undo.pattern);
  139. if(patternExists || onlyChannelSettings)
  140. PrepareBuffer(toBuf, undo.pattern, undo.firstChannel, undo.firstRow, undo.numChannels, undo.numRows, undo.description, linkedFromPrevious, !undo.channelInfo.empty());
  141. const bool modifyChannels = !undo.channelInfo.empty();
  142. const CHANNELINDEX updateChannel = (undo.numChannels == 1) ? undo.firstChannel : CHANNELINDEX_INVALID;
  143. if(modifyChannels)
  144. {
  145. const bool modifyChannelCount =
  146. (undo.pattern != PATTERNINDEX_INVALID && undo.channelInfo.size() != sndFile.GetNumChannels())
  147. || (undo.pattern == PATTERNINDEX_INVALID && (undo.firstChannel + undo.channelInfo.size()) > sndFile.GetNumChannels());
  148. if(modifyChannelCount)
  149. {
  150. // Add or remove channels
  151. std::vector<CHANNELINDEX> channels(undo.channelInfo.size(), CHANNELINDEX_INVALID);
  152. const CHANNELINDEX copyCount = std::min(sndFile.GetNumChannels(), static_cast<CHANNELINDEX>(undo.channelInfo.size()));
  153. std::iota(channels.begin(), channels.begin() + copyCount, CHANNELINDEX(0));
  154. modDoc.ReArrangeChannels(channels, false);
  155. }
  156. if(undo.firstChannel + undo.channelInfo.size() <= sndFile.GetNumChannels())
  157. {
  158. std::move(undo.channelInfo.cbegin(), undo.channelInfo.cend(), std::begin(sndFile.ChnSettings) + undo.firstChannel);
  159. }
  160. // Channel mute status might have changed...
  161. for(CHANNELINDEX i = undo.firstChannel; i < sndFile.GetNumChannels(); i++)
  162. {
  163. modDoc.UpdateChannelMuteStatus(i);
  164. }
  165. }
  166. PATTERNINDEX pat = undo.pattern;
  167. if(deletePattern)
  168. {
  169. sndFile.Patterns.Remove(pat);
  170. } else if(undo.firstChannel + undo.numChannels <= sndFile.GetNumChannels() && !onlyChannelSettings)
  171. {
  172. if(!patternExists)
  173. {
  174. if(!sndFile.Patterns.Insert(pat, undo.numPatternRows))
  175. {
  176. fromBuf.pop_back();
  177. return PATTERNINDEX_INVALID;
  178. }
  179. } else if(sndFile.Patterns[pat].GetNumRows() != undo.numPatternRows)
  180. {
  181. sndFile.Patterns[pat].Resize(undo.numPatternRows);
  182. }
  183. linkToPrevious = undo.linkToPrevious;
  184. auto pUndoData = undo.content.cbegin();
  185. CPattern &pattern = sndFile.Patterns[pat];
  186. ModCommand *m = pattern.GetpModCommand(undo.firstRow, undo.firstChannel);
  187. const ROWINDEX numRows = std::min(undo.numRows, pattern.GetNumRows());
  188. for(ROWINDEX iy = 0; iy < numRows; iy++)
  189. {
  190. std::move(pUndoData, pUndoData + undo.numChannels, m);
  191. m += sndFile.GetNumChannels();
  192. pUndoData += undo.numChannels;
  193. }
  194. }
  195. if(!patternExists && !onlyChannelSettings)
  196. {
  197. // Redo a deletion
  198. auto &redo = fromBuf.back();
  199. redo.content.clear();
  200. redo.numPatternRows = DELETE_PATTERN;
  201. toBuf.push_back(std::move(redo));
  202. }
  203. fromBuf.pop_back();
  204. if(patternExists != sndFile.Patterns.IsValidPat(pat))
  205. {
  206. modDoc.UpdateAllViews(nullptr, PatternHint(pat).Names().Undo());
  207. modDoc.UpdateAllViews(nullptr, SequenceHint().Data()); // Pattern color will change in sequence
  208. } else
  209. {
  210. modDoc.UpdateAllViews(nullptr, UpdateHint().Undo());
  211. }
  212. if(modifyChannels)
  213. modDoc.UpdateAllViews(nullptr, GeneralHint(updateChannel).Channels());
  214. modDoc.SetModified();
  215. if(linkToPrevious)
  216. {
  217. pat = Undo(fromBuf, toBuf, true);
  218. }
  219. return pat;
  220. }
  221. // Public helper function to remove the most recent undo point.
  222. void CPatternUndo::RemoveLastUndoStep()
  223. {
  224. if(UndoBuffer.empty())
  225. return;
  226. UndoBuffer.pop_back();
  227. modDoc.UpdateAllViews(nullptr, UpdateHint().Undo());
  228. }
  229. CString CPatternUndo::GetName(const undobuf_t &buffer)
  230. {
  231. if(buffer.empty())
  232. return CString();
  233. const UndoInfo &info = buffer.back();
  234. CString desc = mpt::ToCString(mpt::Charset::Locale, info.description);
  235. if(info.linkToPrevious)
  236. desc += _T(" (Multiple Patterns)");
  237. else if(info.OnlyChannelSettings() && info.numChannels > 1)
  238. desc += _T(" (Multiple Channels)");
  239. else if(info.OnlyChannelSettings())
  240. desc += MPT_CFORMAT(" (Channel {})")(info.firstChannel + 1);
  241. else
  242. desc += MPT_CFORMAT(" (Pat {} Row {} Chn {})")(info.pattern, info.firstRow, info.firstChannel + 1);
  243. return desc;
  244. }
  245. void CPatternUndo::RearrangePatterns(undobuf_t &buffer, const std::vector<PATTERNINDEX> &newIndex)
  246. {
  247. for(auto &step : buffer)
  248. {
  249. if(step.pattern < newIndex.size())
  250. step.pattern = newIndex[step.pattern];
  251. }
  252. }
  253. void CPatternUndo::RearrangePatterns(const std::vector<PATTERNINDEX> &newIndex)
  254. {
  255. RearrangePatterns(UndoBuffer, newIndex);
  256. RearrangePatterns(RedoBuffer, newIndex);
  257. }
  258. /////////////////////////////////////////////////////////////////////////////////////////
  259. // Sample Undo Functions
  260. // Remove all undo steps for all samples.
  261. void CSampleUndo::ClearUndo()
  262. {
  263. for(SAMPLEINDEX smp = 1; smp <= MAX_SAMPLES; smp++)
  264. {
  265. ClearUndo(UndoBuffer, smp);
  266. ClearUndo(RedoBuffer, smp);
  267. }
  268. UndoBuffer.clear();
  269. RedoBuffer.clear();
  270. }
  271. // Remove all undo steps of a given sample.
  272. void CSampleUndo::ClearUndo(undobuf_t &buffer, const SAMPLEINDEX smp)
  273. {
  274. if(!SampleBufferExists(buffer, smp)) return;
  275. while(!buffer[smp - 1].empty())
  276. {
  277. DeleteStep(buffer, smp, 0);
  278. }
  279. }
  280. // Create undo point for given sample.
  281. // The main program has to tell what kind of changes are going to be made to the sample.
  282. // That way, a lot of RAM can be saved, because some actions don't even require an undo sample buffer.
  283. bool CSampleUndo::PrepareUndo(const SAMPLEINDEX smp, sampleUndoTypes changeType, const char *description, SmpLength changeStart, SmpLength changeEnd)
  284. {
  285. if(PrepareBuffer(UndoBuffer, smp, changeType, description, changeStart, changeEnd))
  286. {
  287. ClearUndo(RedoBuffer, smp);
  288. return true;
  289. }
  290. return false;
  291. }
  292. bool CSampleUndo::PrepareBuffer(undobuf_t &buffer, const SAMPLEINDEX smp, sampleUndoTypes changeType, const char *description, SmpLength changeStart, SmpLength changeEnd)
  293. {
  294. if(smp == 0 || smp >= MAX_SAMPLES) return false;
  295. if(!TrackerSettings::Instance().m_SampleUndoBufferSize.Get().GetSizeInBytes())
  296. {
  297. // Undo/Redo is disabled
  298. return false;
  299. }
  300. if(smp > buffer.size())
  301. {
  302. buffer.resize(smp);
  303. }
  304. // Remove an undo step if there are too many.
  305. while(buffer[smp - 1].size() >= MAX_UNDO_LEVEL)
  306. {
  307. DeleteStep(buffer, smp, 0);
  308. }
  309. // Create new undo slot
  310. UndoInfo undo;
  311. const CSoundFile &sndFile = modDoc.GetSoundFile();
  312. const ModSample &oldSample = sndFile.GetSample(smp);
  313. // Save old sample header
  314. undo.OldSample = oldSample;
  315. undo.oldName = sndFile.m_szNames[smp];
  316. undo.changeType = changeType;
  317. undo.description = description;
  318. if(changeType == sundo_replace)
  319. {
  320. // ensure that size information is correct here.
  321. changeStart = 0;
  322. changeEnd = oldSample.nLength;
  323. } else if(changeType == sundo_none)
  324. {
  325. // we do nothing...
  326. changeStart = changeEnd = 0;
  327. }
  328. if(changeStart > oldSample.nLength || changeStart > changeEnd)
  329. {
  330. // Something is surely screwed up.
  331. MPT_ASSERT(false);
  332. return false;
  333. }
  334. // Restrict amount of memory that's being used
  335. RestrictBufferSize();
  336. undo.changeStart = changeStart;
  337. undo.changeEnd = changeEnd;
  338. undo.samplePtr = nullptr;
  339. switch(changeType)
  340. {
  341. case sundo_none: // we are done, no sample changes here.
  342. case sundo_invert: // no action necessary, since those effects can be applied again to be undone.
  343. case sundo_reverse: // ditto
  344. case sundo_unsign: // ditto
  345. case sundo_insert: // no action necessary, we already have stored the variables that are necessary.
  346. break;
  347. case sundo_update:
  348. case sundo_delete:
  349. case sundo_replace:
  350. if(oldSample.HasSampleData())
  351. {
  352. const uint8 bytesPerSample = oldSample.GetBytesPerSample();
  353. const SmpLength changeLen = changeEnd - changeStart;
  354. undo.samplePtr = ModSample::AllocateSample(changeLen, bytesPerSample);
  355. if(undo.samplePtr == nullptr) return false;
  356. memcpy(undo.samplePtr, oldSample.sampleb() + changeStart * bytesPerSample, changeLen * bytesPerSample);
  357. #ifdef MPT_ALL_LOGGING
  358. const size_t nSize = (GetBufferCapacity(UndoBuffer) + GetBufferCapacity(RedoBuffer) + changeLen * bytesPerSample) >> 10;
  359. MPT_LOG_GLOBAL(LogDebug, "Undo", MPT_UFORMAT("Sample undo/redo buffer size is now {}.{} MB")(nSize >> 10, (nSize & 1023) * 100 / 1024));
  360. #endif
  361. }
  362. break;
  363. default:
  364. MPT_ASSERT(false); // whoops, what's this? someone forgot to implement it, some code is obviously missing here!
  365. return false;
  366. }
  367. buffer[smp - 1].push_back(std::move(undo));
  368. modDoc.UpdateAllViews(nullptr, UpdateHint().Undo());
  369. return true;
  370. }
  371. // Restore undo point for given sample
  372. bool CSampleUndo::Undo(const SAMPLEINDEX smp)
  373. {
  374. return Undo(UndoBuffer, RedoBuffer, smp);
  375. }
  376. // Restore redo point for given sample
  377. bool CSampleUndo::Redo(const SAMPLEINDEX smp)
  378. {
  379. return Undo(RedoBuffer, UndoBuffer, smp);
  380. }
  381. // Restore undo/redo point for given sample
  382. bool CSampleUndo::Undo(undobuf_t &fromBuf, undobuf_t &toBuf, const SAMPLEINDEX smp)
  383. {
  384. if(!SampleBufferExists(fromBuf, smp) || fromBuf[smp - 1].empty()) return false;
  385. CSoundFile &sndFile = modDoc.GetSoundFile();
  386. // 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()
  387. UndoInfo undo = fromBuf[smp - 1].back();
  388. fromBuf[smp - 1].pop_back();
  389. // When turning an undo point into a redo point (and vice versa), some action types need to be adjusted.
  390. sampleUndoTypes redoType = undo.changeType;
  391. if(redoType == sundo_delete)
  392. redoType = sundo_insert;
  393. else if(redoType == sundo_insert)
  394. redoType = sundo_delete;
  395. PrepareBuffer(toBuf, smp, redoType, undo.description, undo.changeStart, undo.changeEnd);
  396. ModSample &sample = sndFile.GetSample(smp);
  397. std::byte *pCurrentSample = mpt::void_cast<std::byte*>(sample.samplev());
  398. int8 *pNewSample = nullptr; // a new sample is possibly going to be allocated, depending on what's going to be undone.
  399. bool keepOnDisk = sample.uFlags[SMP_KEEPONDISK];
  400. bool replace = false;
  401. uint8 bytesPerSample = undo.OldSample.GetBytesPerSample();
  402. SmpLength changeLen = undo.changeEnd - undo.changeStart;
  403. switch(undo.changeType)
  404. {
  405. case sundo_none:
  406. break;
  407. case sundo_invert:
  408. // invert again
  409. SampleEdit::InvertSample(sample, undo.changeStart, undo.changeEnd, sndFile);
  410. break;
  411. case sundo_reverse:
  412. // reverse again
  413. SampleEdit::ReverseSample(sample, undo.changeStart, undo.changeEnd, sndFile);
  414. break;
  415. case sundo_unsign:
  416. // unsign again
  417. SampleEdit::UnsignSample(sample, undo.changeStart, undo.changeEnd, sndFile);
  418. break;
  419. case sundo_insert:
  420. // delete inserted data
  421. MPT_ASSERT(changeLen == sample.nLength - undo.OldSample.nLength);
  422. if(undo.OldSample.nLength > 0)
  423. {
  424. memcpy(pCurrentSample + undo.changeStart * bytesPerSample, pCurrentSample + undo.changeEnd * bytesPerSample, (sample.nLength - undo.changeEnd) * bytesPerSample);
  425. // also clean the sample end
  426. memset(pCurrentSample + undo.OldSample.nLength * bytesPerSample, 0, (sample.nLength - undo.OldSample.nLength) * bytesPerSample);
  427. } else
  428. {
  429. replace = true;
  430. }
  431. break;
  432. case sundo_update:
  433. // simply replace what has been updated.
  434. if(sample.nLength < undo.changeEnd) return false;
  435. memcpy(pCurrentSample + undo.changeStart * bytesPerSample, undo.samplePtr, changeLen * bytesPerSample);
  436. break;
  437. case sundo_delete:
  438. // insert deleted data
  439. pNewSample = static_cast<int8 *>(ModSample::AllocateSample(undo.OldSample.nLength, bytesPerSample));
  440. if(pNewSample == nullptr) return false;
  441. replace = true;
  442. memcpy(pNewSample, pCurrentSample, undo.changeStart * bytesPerSample);
  443. memcpy(pNewSample + undo.changeStart * bytesPerSample, undo.samplePtr, changeLen * bytesPerSample);
  444. memcpy(pNewSample + undo.changeEnd * bytesPerSample, pCurrentSample + undo.changeStart * bytesPerSample, (undo.OldSample.nLength - undo.changeEnd) * bytesPerSample);
  445. break;
  446. case sundo_replace:
  447. // simply exchange sample pointer
  448. pNewSample = static_cast<int8 *>(undo.samplePtr);
  449. undo.samplePtr = nullptr; // prevent sample from being deleted
  450. replace = true;
  451. break;
  452. default:
  453. MPT_ASSERT(false); // whoops, what's this? someone forgot to implement it, some code is obviously missing here!
  454. return false;
  455. }
  456. // Restore old sample header
  457. sample = undo.OldSample;
  458. sample.pData.pSample = mpt::void_cast<void*>(pCurrentSample); // select the "correct" old sample
  459. sndFile.m_szNames[smp] = undo.oldName;
  460. if(replace)
  461. {
  462. ctrlSmp::ReplaceSample(sample, pNewSample, undo.OldSample.nLength, sndFile);
  463. }
  464. sample.PrecomputeLoops(sndFile, true);
  465. if(undo.changeType != sundo_none)
  466. {
  467. sample.uFlags.set(SMP_MODIFIED);
  468. }
  469. if(!keepOnDisk)
  470. {
  471. // Never re-enable the keep on disk flag after it was disabled.
  472. // This can lead to quite some dangerous situations when replacing samples.
  473. sample.uFlags.reset(SMP_KEEPONDISK);
  474. }
  475. fromBuf[smp - 1].push_back(std::move(undo));
  476. DeleteStep(fromBuf, smp, fromBuf[smp - 1].size() - 1);
  477. modDoc.UpdateAllViews(nullptr, UpdateHint().Undo());
  478. modDoc.SetModified();
  479. return true;
  480. }
  481. // Delete a given undo / redo step of a sample.
  482. void CSampleUndo::DeleteStep(undobuf_t &buffer, const SAMPLEINDEX smp, const size_t step)
  483. {
  484. if(!SampleBufferExists(buffer, smp) || step >= buffer[smp - 1].size()) return;
  485. ModSample::FreeSample(buffer[smp - 1][step].samplePtr);
  486. buffer[smp - 1].erase(buffer[smp - 1].begin() + step);
  487. }
  488. // Public helper function to remove the most recent undo point.
  489. void CSampleUndo::RemoveLastUndoStep(const SAMPLEINDEX smp)
  490. {
  491. if(!CanUndo(smp))
  492. return;
  493. DeleteStep(UndoBuffer, smp, UndoBuffer[smp - 1].size() - 1);
  494. modDoc.UpdateAllViews(nullptr, UpdateHint().Undo());
  495. }
  496. // Restrict undo buffer size so it won't grow too large.
  497. // This is done in FIFO style, equally distributed over all sample slots (very simple).
  498. void CSampleUndo::RestrictBufferSize()
  499. {
  500. size_t capacity = GetBufferCapacity(UndoBuffer) + GetBufferCapacity(RedoBuffer);
  501. while(capacity > TrackerSettings::Instance().m_SampleUndoBufferSize.Get().GetSizeInBytes())
  502. {
  503. RestrictBufferSize(UndoBuffer, capacity);
  504. RestrictBufferSize(RedoBuffer, capacity);
  505. }
  506. }
  507. void CSampleUndo::RestrictBufferSize(undobuf_t &buffer, size_t &capacity)
  508. {
  509. for(SAMPLEINDEX smp = 1; smp <= buffer.size(); smp++)
  510. {
  511. if(capacity <= TrackerSettings::Instance().m_SampleUndoBufferSize.Get().GetSizeInBytes()) return;
  512. for(size_t i = 0; i < buffer[smp - 1].size(); i++)
  513. {
  514. if(buffer[smp - 1][i].samplePtr != nullptr)
  515. {
  516. capacity -= (buffer[smp - 1][i].changeEnd - buffer[smp - 1][i].changeStart) * buffer[smp - 1][i].OldSample.GetBytesPerSample();
  517. for(size_t j = 0; j <= i; j++)
  518. {
  519. DeleteStep(buffer, smp, 0);
  520. }
  521. // Try to evenly spread out the restriction, i.e. move on to other samples before deleting another step for this sample.
  522. break;
  523. }
  524. }
  525. }
  526. }
  527. // Update undo buffer when using rearrange sample functionality.
  528. // newIndex contains one new index for each old index. newIndex[1] represents the first sample.
  529. void CSampleUndo::RearrangeSamples(undobuf_t &buffer, const std::vector<SAMPLEINDEX> &newIndex)
  530. {
  531. undobuf_t newBuf(modDoc.GetNumSamples());
  532. const SAMPLEINDEX newSize = static_cast<SAMPLEINDEX>(newIndex.size());
  533. const SAMPLEINDEX oldSize = static_cast<SAMPLEINDEX>(buffer.size());
  534. for(SAMPLEINDEX smp = 1; smp <= oldSize; smp++)
  535. {
  536. MPT_ASSERT(smp >= newSize || newIndex[smp] <= modDoc.GetNumSamples());
  537. if(smp < newSize && newIndex[smp] > 0 && newIndex[smp] <= modDoc.GetNumSamples())
  538. {
  539. newBuf[newIndex[smp] - 1] = buffer[smp - 1];
  540. } else
  541. {
  542. ClearUndo(smp);
  543. }
  544. }
  545. #ifdef _DEBUG
  546. for(size_t i = 0; i < oldSize; i++)
  547. {
  548. if(i + 1 < newIndex.size() && newIndex[i + 1] != 0)
  549. MPT_ASSERT(newBuf[newIndex[i + 1] - 1].size() == buffer[i].size());
  550. else
  551. MPT_ASSERT(buffer[i].empty());
  552. }
  553. #endif
  554. buffer = newBuf;
  555. }
  556. // Return total amount of bytes used by the sample undo buffer.
  557. size_t CSampleUndo::GetBufferCapacity(const undobuf_t &buffer) const
  558. {
  559. size_t sum = 0;
  560. for(auto &smp : buffer)
  561. {
  562. for(auto &step : smp)
  563. {
  564. if(step.samplePtr != nullptr)
  565. {
  566. sum += (step.changeEnd - step.changeStart) * step.OldSample.GetBytesPerSample();
  567. }
  568. }
  569. }
  570. return sum;
  571. }
  572. // Ensure that the undo buffer is big enough for a given sample number
  573. bool CSampleUndo::SampleBufferExists(const undobuf_t &buffer, const SAMPLEINDEX smp) const
  574. {
  575. if(smp == 0 || smp >= MAX_SAMPLES) return false;
  576. if(smp <= buffer.size()) return true;
  577. return false;
  578. }
  579. // Get name of next undo item
  580. const char *CSampleUndo::GetUndoName(const SAMPLEINDEX smp) const
  581. {
  582. if(!CanUndo(smp))
  583. {
  584. return "";
  585. }
  586. return UndoBuffer[smp - 1].back().description;
  587. }
  588. // Get name of next redo item
  589. const char *CSampleUndo::GetRedoName(const SAMPLEINDEX smp) const
  590. {
  591. if(!CanRedo(smp))
  592. {
  593. return "";
  594. }
  595. return RedoBuffer[smp - 1].back().description;
  596. }
  597. /////////////////////////////////////////////////////////////////////////////////////////
  598. // Instrument Undo Functions
  599. // Remove all undo steps for all instruments.
  600. void CInstrumentUndo::ClearUndo()
  601. {
  602. UndoBuffer.clear();
  603. RedoBuffer.clear();
  604. }
  605. // Remove all undo steps of a given instrument.
  606. void CInstrumentUndo::ClearUndo(undobuf_t &buffer, const INSTRUMENTINDEX ins)
  607. {
  608. if(!InstrumentBufferExists(buffer, ins)) return;
  609. buffer[ins - 1].clear();
  610. }
  611. // Create undo point for given Instrument.
  612. // The main program has to tell what kind of changes are going to be made to the Instrument.
  613. // That way, a lot of RAM can be saved, because some actions don't even require an undo Instrument buffer.
  614. bool CInstrumentUndo::PrepareUndo(const INSTRUMENTINDEX ins, const char *description, EnvelopeType envType)
  615. {
  616. if(PrepareBuffer(UndoBuffer, ins, description, envType))
  617. {
  618. ClearUndo(RedoBuffer, ins);
  619. return true;
  620. }
  621. return false;
  622. }
  623. bool CInstrumentUndo::PrepareBuffer(undobuf_t &buffer, const INSTRUMENTINDEX ins, const char *description, EnvelopeType envType)
  624. {
  625. if(ins == 0 || ins >= MAX_INSTRUMENTS || modDoc.GetSoundFile().Instruments[ins] == nullptr) return false;
  626. if(ins > buffer.size())
  627. {
  628. buffer.resize(ins);
  629. }
  630. auto &insBuffer = buffer[ins - 1];
  631. // Remove undo steps if there are too many.
  632. if(insBuffer.size() >= MAX_UNDO_LEVEL)
  633. {
  634. insBuffer.erase(insBuffer.begin(), insBuffer.begin() + (insBuffer.size() - MAX_UNDO_LEVEL + 1));
  635. }
  636. // Create new undo slot
  637. UndoInfo undo;
  638. const CSoundFile &sndFile = modDoc.GetSoundFile();
  639. undo.description = description;
  640. undo.editedEnvelope = envType;
  641. if(envType < ENV_MAXTYPES)
  642. {
  643. undo.instr.GetEnvelope(envType) = sndFile.Instruments[ins]->GetEnvelope(envType);
  644. } else
  645. {
  646. undo.instr = *sndFile.Instruments[ins];
  647. }
  648. // cppcheck false-positive
  649. // cppcheck-suppress uninitStructMember
  650. insBuffer.push_back(std::move(undo));
  651. modDoc.UpdateAllViews(nullptr, UpdateHint().Undo());
  652. return true;
  653. }
  654. // Restore undo point for given Instrument
  655. bool CInstrumentUndo::Undo(const INSTRUMENTINDEX ins)
  656. {
  657. return Undo(UndoBuffer, RedoBuffer, ins);
  658. }
  659. // Restore redo point for given Instrument
  660. bool CInstrumentUndo::Redo(const INSTRUMENTINDEX ins)
  661. {
  662. return Undo(RedoBuffer, UndoBuffer, ins);
  663. }
  664. // Restore undo/redo point for given Instrument
  665. bool CInstrumentUndo::Undo(undobuf_t &fromBuf, undobuf_t &toBuf, const INSTRUMENTINDEX ins)
  666. {
  667. CSoundFile &sndFile = modDoc.GetSoundFile();
  668. if(sndFile.Instruments[ins] == nullptr || !InstrumentBufferExists(fromBuf, ins) || fromBuf[ins - 1].empty()) return false;
  669. // Select most recent undo slot
  670. const UndoInfo &undo = fromBuf[ins - 1].back();
  671. PrepareBuffer(toBuf, ins, undo.description, undo.editedEnvelope);
  672. // When turning an undo point into a redo point (and vice versa), some action types need to be adjusted.
  673. ModInstrument *instr = sndFile.Instruments[ins];
  674. if(undo.editedEnvelope < ENV_MAXTYPES)
  675. {
  676. instr->GetEnvelope(undo.editedEnvelope) = undo.instr.GetEnvelope(undo.editedEnvelope);
  677. } else
  678. {
  679. *instr = undo.instr;
  680. }
  681. DeleteStep(fromBuf, ins, fromBuf[ins - 1].size() - 1);
  682. modDoc.UpdateAllViews(nullptr, UpdateHint().Undo());
  683. modDoc.SetModified();
  684. return true;
  685. }
  686. // Delete a given undo / redo step of a Instrument.
  687. void CInstrumentUndo::DeleteStep(undobuf_t &buffer, const INSTRUMENTINDEX ins, const size_t step)
  688. {
  689. if(!InstrumentBufferExists(buffer, ins) || step >= buffer[ins - 1].size()) return;
  690. buffer[ins - 1].erase(buffer[ins - 1].begin() + step);
  691. }
  692. // Public helper function to remove the most recent undo point.
  693. void CInstrumentUndo::RemoveLastUndoStep(const INSTRUMENTINDEX ins)
  694. {
  695. if(!CanUndo(ins)) return;
  696. DeleteStep(UndoBuffer, ins, UndoBuffer[ins - 1].size() - 1);
  697. }
  698. // Update undo buffer when using rearrange instruments functionality.
  699. // newIndex contains one new index for each old index. newIndex[1] represents the first instrument.
  700. void CInstrumentUndo::RearrangeInstruments(undobuf_t &buffer, const std::vector<INSTRUMENTINDEX> &newIndex)
  701. {
  702. undobuf_t newBuf(modDoc.GetNumInstruments());
  703. const INSTRUMENTINDEX newSize = static_cast<INSTRUMENTINDEX>(newIndex.size());
  704. const INSTRUMENTINDEX oldSize = static_cast<INSTRUMENTINDEX>(buffer.size());
  705. for(INSTRUMENTINDEX ins = 1; ins <= oldSize; ins++)
  706. {
  707. MPT_ASSERT(ins >= newSize || newIndex[ins] <= modDoc.GetNumInstruments());
  708. if(ins < newSize && newIndex[ins] > 0 && newIndex[ins] <= modDoc.GetNumInstruments())
  709. {
  710. newBuf[newIndex[ins] - 1] = buffer[ins - 1];
  711. } else
  712. {
  713. ClearUndo(ins);
  714. }
  715. }
  716. #ifdef _DEBUG
  717. for(size_t i = 0; i < oldSize; i++)
  718. {
  719. if(i + 1 < newIndex.size() && newIndex[i + 1] != 0)
  720. MPT_ASSERT(newBuf[newIndex[i + 1] - 1].size() == buffer[i].size());
  721. else
  722. MPT_ASSERT(buffer[i].empty());
  723. }
  724. #endif
  725. buffer = newBuf;
  726. }
  727. // Update undo buffer when using rearrange samples functionality.
  728. // newIndex contains one new index for each old index. newIndex[1] represents the first sample.
  729. void CInstrumentUndo::RearrangeSamples(undobuf_t &buffer, const INSTRUMENTINDEX ins, std::vector<SAMPLEINDEX> &newIndex)
  730. {
  731. const CSoundFile &sndFile = modDoc.GetSoundFile();
  732. if(sndFile.Instruments[ins] == nullptr || !InstrumentBufferExists(buffer, ins) || buffer[ins - 1].empty()) return;
  733. for(auto &i : buffer[ins - 1]) if(i.editedEnvelope >= ENV_MAXTYPES)
  734. {
  735. for(auto &sample : i.instr.Keyboard)
  736. {
  737. if(sample < newIndex.size())
  738. sample = newIndex[sample];
  739. else
  740. sample = 0;
  741. }
  742. }
  743. }
  744. // Ensure that the undo buffer is big enough for a given Instrument number
  745. bool CInstrumentUndo::InstrumentBufferExists(const undobuf_t &buffer, const INSTRUMENTINDEX ins) const
  746. {
  747. if(ins == 0 || ins >= MAX_INSTRUMENTS) return false;
  748. if(ins <= buffer.size()) return true;
  749. return false;
  750. }
  751. // Get name of next undo item
  752. const char *CInstrumentUndo::GetUndoName(const INSTRUMENTINDEX ins) const
  753. {
  754. if(!CanUndo(ins))
  755. {
  756. return "";
  757. }
  758. return UndoBuffer[ins - 1].back().description;
  759. }
  760. // Get name of next redo item
  761. const char *CInstrumentUndo::GetRedoName(const INSTRUMENTINDEX ins) const
  762. {
  763. if(!CanRedo(ins))
  764. {
  765. return "";
  766. }
  767. return RedoBuffer[ins - 1].back().description;
  768. }
  769. OPENMPT_NAMESPACE_END