aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/openmpt-trunk/soundlib/ModSequence.cpp
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/soundlib/ModSequence.cpp
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/soundlib/ModSequence.cpp')
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/ModSequence.cpp673
1 files changed, 673 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/ModSequence.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/ModSequence.cpp
new file mode 100644
index 00000000..b37eac13
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/ModSequence.cpp
@@ -0,0 +1,673 @@
+/*
+ * ModSequence.cpp
+ * ---------------
+ * Purpose: Order and sequence handling.
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+#include "ModSequence.h"
+#include "Sndfile.h"
+#include "mod_specifications.h"
+#include "../common/version.h"
+#include "../common/serialization_utils.h"
+#include "mpt/io/io.hpp"
+#include "mpt/io/io_stdstream.hpp"
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+ModSequence::ModSequence(CSoundFile &sndFile)
+ : m_sndFile(sndFile)
+{
+}
+
+
+ModSequence& ModSequence::operator=(const ModSequence &other)
+{
+ MPT_ASSERT(&other.m_sndFile == &m_sndFile);
+ if(&other == this)
+ return *this;
+ std::vector<PATTERNINDEX>::assign(other.begin(), other.end());
+ m_name = other.m_name;
+ m_restartPos = other.m_restartPos;
+ return *this;
+}
+
+
+bool ModSequence::operator== (const ModSequence &other) const
+{
+ return static_cast<const std::vector<PATTERNINDEX> &>(*this) == other
+ && m_name == other.m_name
+ && m_restartPos == other.m_restartPos;
+}
+
+
+bool ModSequence::NeedsExtraDatafield() const
+{
+ return (m_sndFile.GetType() == MOD_TYPE_MPT && m_sndFile.Patterns.GetNumPatterns() > 0xFD);
+}
+
+
+void ModSequence::AdjustToNewModType(const MODTYPE oldtype)
+{
+ auto &specs = m_sndFile.GetModSpecifications();
+
+ if(oldtype != MOD_TYPE_NONE)
+ {
+ // If not supported, remove "+++" separator order items.
+ if(!specs.hasIgnoreIndex)
+ {
+ RemovePattern(GetIgnoreIndex());
+ }
+ // If not supported, remove "---" items between patterns.
+ if(!specs.hasStopIndex)
+ {
+ RemovePattern(GetInvalidPatIndex());
+ }
+ }
+
+ //Resize orderlist if needed.
+ if(specs.ordersMax < size())
+ {
+ // Order list too long? Remove "unnecessary" order items first.
+ if(oldtype != MOD_TYPE_NONE && specs.ordersMax < GetLengthTailTrimmed())
+ {
+ erase(std::remove_if(begin(), end(), [&] (PATTERNINDEX pat) { return !m_sndFile.Patterns.IsValidPat(pat); }), end());
+ if(GetLengthTailTrimmed() > specs.ordersMax)
+ {
+ m_sndFile.AddToLog(LogWarning, U_("WARNING: Order list has been trimmed!"));
+ }
+ }
+ resize(specs.ordersMax);
+ }
+}
+
+
+ORDERINDEX ModSequence::GetLengthTailTrimmed() const
+{
+ if(empty())
+ return 0;
+ auto last = std::find_if(rbegin(), rend(), [] (PATTERNINDEX pat) { return pat != GetInvalidPatIndex(); });
+ return static_cast<ORDERINDEX>(std::distance(begin(), last.base()));
+}
+
+
+ORDERINDEX ModSequence::GetLengthFirstEmpty() const
+{
+ return static_cast<ORDERINDEX>(std::distance(begin(), std::find(begin(), end(), GetInvalidPatIndex())));
+}
+
+
+ORDERINDEX ModSequence::GetNextOrderIgnoringSkips(const ORDERINDEX start) const
+{
+ if(empty())
+ return 0;
+ auto length = GetLength();
+ ORDERINDEX next = std::min(ORDERINDEX(length - 1), ORDERINDEX(start + 1));
+ while(next + 1 < length && at(next) == GetIgnoreIndex()) next++;
+ return next;
+}
+
+
+ORDERINDEX ModSequence::GetPreviousOrderIgnoringSkips(const ORDERINDEX start) const
+{
+ const ORDERINDEX last = GetLastIndex();
+ if(start == 0 || last == 0) return 0;
+ ORDERINDEX prev = std::min(ORDERINDEX(start - 1), last);
+ while(prev > 0 && at(prev) == GetIgnoreIndex()) prev--;
+ return prev;
+}
+
+
+void ModSequence::Remove(ORDERINDEX posBegin, ORDERINDEX posEnd)
+{
+ if(posEnd < posBegin || posEnd >= size())
+ return;
+ erase(begin() + posBegin, begin() + posEnd + 1);
+}
+
+
+// Remove all references to a given pattern index from the order list. Jump commands are updated accordingly.
+void ModSequence::RemovePattern(PATTERNINDEX pat)
+{
+ // First, calculate the offset that needs to be applied to jump commands
+ const ORDERINDEX orderLength = GetLengthTailTrimmed();
+ std::vector<ORDERINDEX> newPosition(orderLength);
+ ORDERINDEX maxJump = 0;
+ for(ORDERINDEX i = 0; i < orderLength; i++)
+ {
+ newPosition[i] = i - maxJump;
+ if(at(i) == pat)
+ {
+ maxJump++;
+ }
+ }
+ if(!maxJump)
+ {
+ return;
+ }
+
+ erase(std::remove(begin(), end(), pat), end());
+
+ // Only apply to patterns actually found in this sequence
+ for(auto p : *this) if(m_sndFile.Patterns.IsValidPat(p))
+ {
+ for(auto &m : m_sndFile.Patterns[p])
+ {
+ if(m.command == CMD_POSITIONJUMP && m.param < newPosition.size())
+ {
+ m.param = static_cast<ModCommand::PARAM>(newPosition[m.param]);
+ }
+ }
+ }
+ if(m_restartPos < newPosition.size())
+ {
+ m_restartPos = newPosition[m_restartPos];
+ }
+}
+
+
+void ModSequence::assign(ORDERINDEX newSize, PATTERNINDEX pat)
+{
+ LimitMax(newSize, m_sndFile.GetModSpecifications().ordersMax);
+ std::vector<PATTERNINDEX>::assign(newSize, pat);
+}
+
+
+ORDERINDEX ModSequence::insert(ORDERINDEX pos, ORDERINDEX count, PATTERNINDEX fill)
+{
+ const auto ordersMax = m_sndFile.GetModSpecifications().ordersMax;
+ if(pos >= ordersMax || GetLengthTailTrimmed() >= ordersMax || count == 0)
+ return 0;
+ // Limit number of orders to be inserted so that we don't exceed the format limit.
+ LimitMax(count, static_cast<ORDERINDEX>(ordersMax - pos));
+ reserve(std::max(pos, GetLength()) + count);
+ // Inserting past the end of the container?
+ if(pos > size())
+ resize(pos);
+ std::vector<PATTERNINDEX>::insert(begin() + pos, count, fill);
+ // Did we overgrow? Remove patterns at end.
+ if(size() > ordersMax)
+ resize(ordersMax);
+ return count;
+}
+
+
+bool ModSequence::IsValidPat(ORDERINDEX ord) const
+{
+ if(ord < size())
+ return m_sndFile.Patterns.IsValidPat(at(ord));
+ return false;
+}
+
+
+CPattern *ModSequence::PatternAt(ORDERINDEX ord) const
+{
+ if(!IsValidPat(ord))
+ return nullptr;
+ return &m_sndFile.Patterns[at(ord)];
+}
+
+
+ORDERINDEX ModSequence::FindOrder(PATTERNINDEX pat, ORDERINDEX startSearchAt, bool searchForward) const
+{
+ const ORDERINDEX length = GetLength();
+ if(startSearchAt >= length)
+ return ORDERINDEX_INVALID;
+ ORDERINDEX ord = startSearchAt;
+ for(ORDERINDEX p = 0; p < length; p++)
+ {
+ if(at(ord) == pat)
+ {
+ return ord;
+ }
+ if(searchForward)
+ {
+ if(++ord >= length)
+ ord = 0;
+ } else
+ {
+ if(ord-- == 0)
+ ord = length - 1;
+ }
+ }
+ return ORDERINDEX_INVALID;
+}
+
+
+PATTERNINDEX ModSequence::EnsureUnique(ORDERINDEX ord)
+{
+ PATTERNINDEX pat = at(ord);
+ if(!IsValidPat(ord))
+ return pat;
+
+ for(const auto &sequence : m_sndFile.Order)
+ {
+ ORDERINDEX ords = sequence.GetLength();
+ for(ORDERINDEX o = 0; o < ords; o++)
+ {
+ if(sequence[o] == pat && (o != ord || &sequence != this))
+ {
+ // Found duplicate usage.
+ PATTERNINDEX newPat = m_sndFile.Patterns.Duplicate(pat);
+ if(newPat != PATTERNINDEX_INVALID)
+ {
+ at(ord) = newPat;
+ return newPat;
+ }
+ }
+ }
+ }
+ return pat;
+}
+
+
+/////////////////////////////////////
+// ModSequenceSet
+/////////////////////////////////////
+
+
+ModSequenceSet::ModSequenceSet(CSoundFile &sndFile)
+ : m_sndFile(sndFile)
+{
+ Initialize();
+}
+
+
+void ModSequenceSet::Initialize()
+{
+ m_currentSeq = 0;
+ m_Sequences.assign(1, ModSequence(m_sndFile));
+}
+
+
+void ModSequenceSet::SetSequence(SEQUENCEINDEX n)
+{
+ if(n < m_Sequences.size())
+ m_currentSeq = n;
+}
+
+
+SEQUENCEINDEX ModSequenceSet::AddSequence()
+{
+ if(GetNumSequences() >= MAX_SEQUENCES)
+ return SEQUENCEINDEX_INVALID;
+ m_Sequences.push_back(ModSequence{m_sndFile});
+ SetSequence(GetNumSequences() - 1);
+ return GetNumSequences() - 1;
+}
+
+
+void ModSequenceSet::RemoveSequence(SEQUENCEINDEX i)
+{
+ // Do nothing if index is invalid or if there's only one sequence left.
+ if(i >= m_Sequences.size() || m_Sequences.size() <= 1)
+ return;
+ m_Sequences.erase(m_Sequences.begin() + i);
+ if(i < m_currentSeq || m_currentSeq >= GetNumSequences())
+ m_currentSeq--;
+}
+
+
+#ifdef MODPLUG_TRACKER
+
+bool ModSequenceSet::Rearrange(const std::vector<SEQUENCEINDEX> &newOrder)
+{
+ if(newOrder.empty() || newOrder.size() > MAX_SEQUENCES)
+ return false;
+
+ const auto oldSequences = std::move(m_Sequences);
+ m_Sequences.assign(newOrder.size(), ModSequence{m_sndFile});
+ for(size_t i = 0; i < newOrder.size(); i++)
+ {
+ if(newOrder[i] < oldSequences.size())
+ m_Sequences[i] = oldSequences[newOrder[i]];
+ }
+
+ if(m_currentSeq > m_Sequences.size())
+ m_currentSeq = GetNumSequences() - 1u;
+ return true;
+}
+
+
+void ModSequenceSet::OnModTypeChanged(MODTYPE oldType)
+{
+ for(auto &seq : m_Sequences)
+ {
+ seq.AdjustToNewModType(oldType);
+ }
+ if(m_sndFile.GetModSpecifications(oldType).sequencesMax > 1 && m_sndFile.GetModSpecifications().sequencesMax <= 1)
+ MergeSequences();
+}
+
+
+bool ModSequenceSet::CanSplitSubsongs() const
+{
+ return GetNumSequences() == 1 && m_sndFile.GetModSpecifications().sequencesMax > 1 && m_Sequences[0].HasSubsongs();
+}
+
+
+bool ModSequenceSet::SplitSubsongsToMultipleSequences()
+{
+ if(!CanSplitSubsongs())
+ return false;
+
+ bool modified = false;
+ const ORDERINDEX length = m_Sequences[0].GetLengthTailTrimmed();
+
+ for(ORDERINDEX ord = 0; ord < length; ord++)
+ {
+ // End of subsong?
+ if(!m_Sequences[0].IsValidPat(ord) && m_Sequences[0][ord] != GetIgnoreIndex())
+ {
+ // Remove all separator patterns between current and next subsong first
+ while(ord < length && !m_sndFile.Patterns.IsValidPat(m_Sequences[0][ord]))
+ {
+ m_Sequences[0][ord] = GetInvalidPatIndex();
+ ord++;
+ modified = true;
+ }
+ if(ord >= length)
+ break;
+
+ const SEQUENCEINDEX newSeq = AddSequence();
+ if(newSeq == SEQUENCEINDEX_INVALID)
+ break;
+
+ const ORDERINDEX startOrd = ord;
+ m_Sequences[newSeq].reserve(length - startOrd);
+ modified = true;
+
+ // Now, move all following orders to the new sequence
+ while(ord < length && m_Sequences[0][ord] != GetInvalidPatIndex())
+ {
+ PATTERNINDEX copyPat = m_Sequences[0][ord];
+ m_Sequences[newSeq].push_back(copyPat);
+ m_Sequences[0][ord] = GetInvalidPatIndex();
+ ord++;
+
+ // Is this a valid pattern? adjust pattern jump commands, if necessary.
+ if(m_sndFile.Patterns.IsValidPat(copyPat))
+ {
+ for(auto &m : m_sndFile.Patterns[copyPat])
+ {
+ if(m.command == CMD_POSITIONJUMP && m.param >= startOrd)
+ {
+ m.param = static_cast<ModCommand::PARAM>(m.param - startOrd);
+ }
+ }
+ }
+ }
+ ord--;
+ }
+ }
+ SetSequence(0);
+ return modified;
+}
+
+
+// Convert the sequence's restart position information to a pattern command.
+bool ModSequenceSet::RestartPosToPattern(SEQUENCEINDEX seq)
+{
+ bool result = false;
+ auto length = m_sndFile.GetLength(eNoAdjust, GetLengthTarget(true).StartPos(seq, 0, 0));
+ ModSequence &order = m_Sequences[seq];
+ for(const auto &subSong : length)
+ {
+ if(subSong.endOrder != ORDERINDEX_INVALID && subSong.endRow != ROWINDEX_INVALID)
+ {
+ if(mpt::in_range<ModCommand::PARAM>(order.GetRestartPos()))
+ {
+ PATTERNINDEX writePat = order.EnsureUnique(subSong.endOrder);
+ result = m_sndFile.Patterns[writePat].WriteEffect(
+ EffectWriter(CMD_POSITIONJUMP, static_cast<ModCommand::PARAM>(order.GetRestartPos())).Row(subSong.endRow).RetryNextRow());
+ } else
+ {
+ result = false;
+ }
+ }
+ }
+ order.SetRestartPos(0);
+ return result;
+}
+
+
+bool ModSequenceSet::MergeSequences()
+{
+ if(GetNumSequences() <= 1)
+ return false;
+
+ ModSequence &firstSeq = m_Sequences[0];
+ firstSeq.resize(firstSeq.GetLengthTailTrimmed());
+ std::vector<SEQUENCEINDEX> patternsFixed(m_sndFile.Patterns.Size(), SEQUENCEINDEX_INVALID); // pattern fixed by other sequence already?
+ // Mark patterns handled in first sequence
+ for(auto pat : firstSeq)
+ {
+ if(m_sndFile.Patterns.IsValidPat(pat))
+ patternsFixed[pat] = 0;
+ }
+
+ for(SEQUENCEINDEX seqNum = 1; seqNum < GetNumSequences(); seqNum++)
+ {
+ ModSequence &seq = m_Sequences[seqNum];
+ const ORDERINDEX firstOrder = firstSeq.GetLength() + 1; // +1 for separator item
+ const ORDERINDEX lengthTrimmed = seq.GetLengthTailTrimmed();
+ if(firstOrder + lengthTrimmed > m_sndFile.GetModSpecifications().ordersMax)
+ {
+ m_sndFile.AddToLog(LogWarning, MPT_UFORMAT("WARNING: Cannot merge Sequence {} (too long!)")(seqNum + 1));
+ continue;
+ }
+ firstSeq.reserve(firstOrder + lengthTrimmed);
+ firstSeq.push_back(); // Separator item
+ RestartPosToPattern(seqNum);
+ for(ORDERINDEX ord = 0; ord < lengthTrimmed; ord++)
+ {
+ PATTERNINDEX pat = seq[ord];
+ firstSeq.push_back(pat);
+
+ // Try to fix pattern jump commands
+ if(!m_sndFile.Patterns.IsValidPat(pat)) continue;
+
+ auto m = m_sndFile.Patterns[pat].begin();
+ for(size_t len = 0; len < m_sndFile.Patterns[pat].GetNumRows() * m_sndFile.m_nChannels; m++, len++)
+ {
+ if(m->command == CMD_POSITIONJUMP)
+ {
+ if(patternsFixed[pat] != SEQUENCEINDEX_INVALID && patternsFixed[pat] != seqNum)
+ {
+ // Oops, some other sequence uses this pattern already.
+ const PATTERNINDEX newPat = m_sndFile.Patterns.Duplicate(pat, true);
+ if(newPat != PATTERNINDEX_INVALID)
+ {
+ // Could create new pattern - copy data over and continue from here.
+ firstSeq[firstOrder + ord] = newPat;
+ m = m_sndFile.Patterns[newPat].begin() + len;
+ if(newPat >= patternsFixed.size())
+ patternsFixed.resize(newPat + 1, SEQUENCEINDEX_INVALID);
+ pat = newPat;
+ } else
+ {
+ // Cannot create new pattern: notify the user
+ m_sndFile.AddToLog(LogWarning, MPT_UFORMAT("CONFLICT: Pattern break commands in Pattern {} might be broken since it has been used in several sequences!")(pat));
+ }
+ }
+ m->param = static_cast<ModCommand::PARAM>(m->param + firstOrder);
+ patternsFixed[pat] = seqNum;
+ }
+ }
+ }
+ }
+ m_Sequences.erase(m_Sequences.begin() + 1, m_Sequences.end());
+ return true;
+}
+
+
+// Check if a playback position is currently locked (inaccessible)
+bool ModSequence::IsPositionLocked(ORDERINDEX position) const
+{
+ return(m_sndFile.m_lockOrderStart != ORDERINDEX_INVALID
+ && (position < m_sndFile.m_lockOrderStart || position > m_sndFile.m_lockOrderEnd));
+}
+
+
+bool ModSequence::HasSubsongs() const
+{
+ const auto endPat = begin() + GetLengthTailTrimmed();
+ return std::find_if(begin(), endPat,
+ [&](PATTERNINDEX pat) { return pat != GetIgnoreIndex() && !m_sndFile.Patterns.IsValidPat(pat); }) != endPat;
+}
+#endif // MODPLUG_TRACKER
+
+
+/////////////////////////////////////
+// Read/Write
+/////////////////////////////////////
+
+
+#ifndef MODPLUG_NO_FILESAVE
+size_t ModSequence::WriteAsByte(std::ostream &f, const ORDERINDEX count, uint8 stopIndex, uint8 ignoreIndex) const
+{
+ const size_t limit = std::min(count, GetLength());
+
+ for(size_t i = 0; i < limit; i++)
+ {
+ const PATTERNINDEX pat = at(i);
+ uint8 temp = static_cast<uint8>(pat);
+
+ if(pat == GetInvalidPatIndex()) temp = stopIndex;
+ else if(pat == GetIgnoreIndex() || pat > 0xFF) temp = ignoreIndex;
+ mpt::IO::WriteIntLE<uint8>(f, temp);
+ }
+ // Fill non-existing order items with stop indices
+ for(size_t i = limit; i < count; i++)
+ {
+ mpt::IO::WriteIntLE<uint8>(f, stopIndex);
+ }
+ return count; //Returns the number of bytes written.
+}
+#endif // MODPLUG_NO_FILESAVE
+
+
+void ReadModSequenceOld(std::istream& iStrm, ModSequenceSet& seq, const size_t)
+{
+ uint16 size;
+ mpt::IO::ReadIntLE<uint16>(iStrm, size);
+ if(size > ModSpecs::mptm.ordersMax)
+ {
+ seq.m_sndFile.AddToLog(LogWarning, MPT_UFORMAT("Module has sequence of length {}; it will be truncated to maximum supported length, {}.")(size, ModSpecs::mptm.ordersMax));
+ size = ModSpecs::mptm.ordersMax;
+ }
+ seq(0).resize(size);
+ for(auto &pat : seq(0))
+ {
+ uint16 temp;
+ mpt::IO::ReadIntLE<uint16>(iStrm, temp);
+ pat = temp;
+ }
+}
+
+
+#ifndef MODPLUG_NO_FILESAVE
+void WriteModSequenceOld(std::ostream& oStrm, const ModSequenceSet& seq)
+{
+ const uint16 size = seq().GetLength();
+ mpt::IO::WriteIntLE<uint16>(oStrm, size);
+ for(auto pat : seq())
+ {
+ mpt::IO::WriteIntLE<uint16>(oStrm, static_cast<uint16>(pat));
+ }
+}
+#endif // MODPLUG_NO_FILESAVE
+
+
+#ifndef MODPLUG_NO_FILESAVE
+void WriteModSequence(std::ostream& oStrm, const ModSequence& seq)
+{
+ srlztn::SsbWrite ssb(oStrm);
+ ssb.BeginWrite(FileIdSequence, Version::Current().GetRawVersion());
+ int8 useUTF8 = 1;
+ ssb.WriteItem(useUTF8, "u");
+ ssb.WriteItem(mpt::ToCharset(mpt::Charset::UTF8, seq.GetName()), "n");
+ const uint16 length = seq.GetLengthTailTrimmed();
+ ssb.WriteItem<uint16>(length, "l");
+ ssb.WriteItem(seq, "a", srlztn::VectorWriter<uint16>(length));
+ if(seq.GetRestartPos() > 0)
+ ssb.WriteItem<uint16>(seq.GetRestartPos(), "r");
+ ssb.FinishWrite();
+}
+#endif // MODPLUG_NO_FILESAVE
+
+
+void ReadModSequence(std::istream& iStrm, ModSequence& seq, const size_t, mpt::Charset defaultCharset)
+{
+ srlztn::SsbRead ssb(iStrm);
+ ssb.BeginRead(FileIdSequence, Version::Current().GetRawVersion());
+ if ((ssb.GetStatus() & srlztn::SNT_FAILURE) != 0)
+ return;
+ int8 useUTF8 = 0;
+ ssb.ReadItem(useUTF8, "u");
+ std::string str;
+ ssb.ReadItem(str, "n");
+ seq.SetName(mpt::ToUnicode(useUTF8 ? mpt::Charset::UTF8 : defaultCharset, str));
+ ORDERINDEX nSize = 0;
+ ssb.ReadItem(nSize, "l");
+ LimitMax(nSize, ModSpecs::mptm.ordersMax);
+ ssb.ReadItem(seq, "a", srlztn::VectorReader<uint16>(nSize));
+
+ ORDERINDEX restartPos = ORDERINDEX_INVALID;
+ if(ssb.ReadItem(restartPos, "r") != srlztn::SsbRead::EntryNotFound && restartPos < nSize)
+ seq.SetRestartPos(restartPos);
+}
+
+
+#ifndef MODPLUG_NO_FILESAVE
+void WriteModSequences(std::ostream& oStrm, const ModSequenceSet& seq)
+{
+ srlztn::SsbWrite ssb(oStrm);
+ ssb.BeginWrite(FileIdSequences, Version::Current().GetRawVersion());
+ const uint8 nSeqs = seq.GetNumSequences();
+ const uint8 nCurrent = seq.GetCurrentSequenceIndex();
+ ssb.WriteItem(nSeqs, "n");
+ ssb.WriteItem(nCurrent, "c");
+ for(uint8 i = 0; i < nSeqs; i++)
+ {
+ ssb.WriteItem(seq(i), srlztn::ID::FromInt<uint8>(i), &WriteModSequence);
+ }
+ ssb.FinishWrite();
+}
+#endif // MODPLUG_NO_FILESAVE
+
+
+void ReadModSequences(std::istream& iStrm, ModSequenceSet& seq, const size_t, mpt::Charset defaultCharset)
+{
+ srlztn::SsbRead ssb(iStrm);
+ ssb.BeginRead(FileIdSequences, Version::Current().GetRawVersion());
+ if ((ssb.GetStatus() & srlztn::SNT_FAILURE) != 0)
+ return;
+ SEQUENCEINDEX seqs = 0;
+ uint8 currentSeq = 0;
+ ssb.ReadItem(seqs, "n");
+ if (seqs == 0)
+ return;
+ LimitMax(seqs, MAX_SEQUENCES);
+ ssb.ReadItem(currentSeq, "c");
+ if (seq.GetNumSequences() < seqs)
+ seq.m_Sequences.resize(seqs, ModSequence(seq.m_sndFile));
+
+ // There used to be only one restart position for all sequences
+ ORDERINDEX legacyRestartPos = seq(0).GetRestartPos();
+
+ for(SEQUENCEINDEX i = 0; i < seqs; i++)
+ {
+ seq(i).SetRestartPos(legacyRestartPos);
+ ssb.ReadItem(seq(i), srlztn::ID::FromInt<uint8>(i), [defaultCharset](std::istream &iStrm, ModSequence &seq, std::size_t dummy) { return ReadModSequence(iStrm, seq, dummy, defaultCharset); });
+ }
+ seq.m_currentSeq = (currentSeq < seq.GetNumSequences()) ? currentSeq : 0;
+}
+
+
+OPENMPT_NAMESPACE_END