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/soundlib/ModSequence.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-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.cpp | 673 |
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 |