Cog/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.cpp
Christopher Snowhill da1973bcd9 Build libOpenMPT from source once again
Bundle libOpenMPT as a dynamic framework, which should be safe once
again, now that there is only one version to bundle. Also, now it is
using the versions of libvorbisfile and libmpg123 that are bundled with
the player, instead of compiling minimp3 and stbvorbis.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-30 22:56:52 -07:00

673 lines
18 KiB
C++

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