1 /*
2  * ModSequence.h
3  * -------------
4  * Purpose: Order and sequence handling.
5  * Notes  : (currently none)
6  * Authors: OpenMPT Devs
7  * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
8  */
9 
10 
11 #pragma once
12 
13 #include "openmpt/all/BuildSettings.hpp"
14 
15 #include "Snd_defs.h"
16 
17 #include <algorithm>
18 #include <vector>
19 
20 OPENMPT_NAMESPACE_BEGIN
21 
22 class CPattern;
23 class CSoundFile;
24 class ModSequenceSet;
25 
26 class ModSequence: public std::vector<PATTERNINDEX>
27 {
28 	friend class ModSequenceSet;
29 
30 protected:
31 	mpt::ustring m_name;          // Sequence name
32 	CSoundFile &m_sndFile;        // Associated CSoundFile
33 	ORDERINDEX m_restartPos = 0;  // Restart position when playback of this order ended
34 
35 public:
36 	ModSequence(CSoundFile &sndFile);
37 	ModSequence(ModSequence &&) noexcept = default;
38 	ModSequence(const ModSequence &) = default;
39 	ModSequence& operator=(const ModSequence &other);
40 
41 	bool operator==(const ModSequence &other) const;
42 	bool operator!=(const ModSequence &other) const { return !(*this == other); }
43 
GetLength()44 	ORDERINDEX GetLength() const { return mpt::saturate_cast<ORDERINDEX>(size()); }
45 	// Returns last accessible index, i.e. GetLength() - 1, or 0 if the order list is empty.
GetLastIndex()46 	ORDERINDEX GetLastIndex() const { return std::max(ORDERINDEX(1), GetLength()) - 1u; }
47 	// Returns length of sequence without counting trailing '---' items.
48 	ORDERINDEX GetLengthTailTrimmed() const;
49 	// Returns length of sequence stopping counting on first '---' (or at the end of sequence).
50 	ORDERINDEX GetLengthFirstEmpty() const;
51 
52 	// Replaces order list with 'newSize' copies of 'pat'.
53 	void assign(ORDERINDEX newSize, PATTERNINDEX pat);
54 
55 	// Inserts 'count' orders starting from 'pos' using 'fill' as the pattern index for all inserted orders.
56 	// Sequence will automatically grow if needed and if it can't grow enough, some tail orders will be discarded.
57 	// Return: Number of orders inserted (up to 'count' many).
insert(ORDERINDEX pos,ORDERINDEX count)58 	ORDERINDEX insert(ORDERINDEX pos, ORDERINDEX count) { return insert(pos, count, GetInvalidPatIndex()); }
59 	ORDERINDEX insert(ORDERINDEX pos, ORDERINDEX count, PATTERNINDEX fill);
60 
push_back()61 	void push_back() { push_back(GetInvalidPatIndex()); }
push_back(PATTERNINDEX pat)62 	void push_back(PATTERNINDEX pat) { if(GetLength() < MAX_ORDERS) std::vector<PATTERNINDEX>::push_back(pat); }
63 
resize(ORDERINDEX newSize)64 	void resize(ORDERINDEX newSize) { resize(newSize, GetInvalidPatIndex()); }
resize(ORDERINDEX newSize,PATTERNINDEX pat)65 	void resize(ORDERINDEX newSize, PATTERNINDEX pat) { std::vector<PATTERNINDEX>::resize(std::min(MAX_ORDERS, newSize), pat); }
66 
67 	// Removes orders from range [posBegin, posEnd].
68 	void Remove(ORDERINDEX posBegin, ORDERINDEX posEnd);
69 
70 	// Remove all references to a given pattern index from the order list. Jump commands are updated accordingly.
71 	void RemovePattern(PATTERNINDEX pat);
72 
73 	// Replaces all occurences of oldPat with newPat.
Replace(PATTERNINDEX oldPat,PATTERNINDEX newPat)74 	void Replace(PATTERNINDEX oldPat, PATTERNINDEX newPat) { if(oldPat != newPat) std::replace(begin(), end(), oldPat, newPat); }
75 
76 	// Removes any "---" patterns at the end of the list.
Shrink()77 	void Shrink() { resize(GetLengthTailTrimmed()); }
78 
79 	// Check if pattern at sequence position ord is valid.
80 	bool IsValidPat(ORDERINDEX ord) const;
81 
82 	CPattern *PatternAt(ORDERINDEX ord) const;
83 
84 	void AdjustToNewModType(const MODTYPE oldtype);
85 
86 	// Returns the internal representation of a stop '---' index
GetInvalidPatIndex()87 	static constexpr PATTERNINDEX GetInvalidPatIndex() { return uint16_max; }
88 	// Returns the internal representation of an ignore '+++' index
GetIgnoreIndex()89 	static constexpr PATTERNINDEX GetIgnoreIndex() { return uint16_max - 1; }
90 
91 	// Returns the previous/next order ignoring skip indices (+++).
92 	// If no previous/next order exists, return first/last order, and zero
93 	// when orderlist is empty.
94 	ORDERINDEX GetPreviousOrderIgnoringSkips(const ORDERINDEX start) const;
95 	ORDERINDEX GetNextOrderIgnoringSkips(const ORDERINDEX start) const;
96 
97 	// Find an order item that contains a given pattern number.
98 	ORDERINDEX FindOrder(PATTERNINDEX pat, ORDERINDEX startSearchAt = 0, bool searchForward = true) const;
99 
100 	// Ensures that the pattern at the specified order position is used only once (across all sequences).
101 	// If another usage is found, the pattern is replaced by a copy and the new index is returned.
102 	PATTERNINDEX EnsureUnique(ORDERINDEX ord);
103 
104 #ifndef MODPLUG_NO_FILESAVE
105 	// Write order items as bytes. '---' is written as stopIndex, '+++' is written as ignoreIndex
106 	size_t WriteAsByte(std::ostream &f, const ORDERINDEX count, uint8 stopIndex = 0xFF, uint8 ignoreIndex = 0xFE) const;
107 #endif // MODPLUG_NO_FILESAVE
108 
109 	// Returns true if the IT orderlist datafield is not sufficient to store orderlist information.
110 	bool NeedsExtraDatafield() const;
111 
112 #ifdef MODPLUG_TRACKER
113 	// Check if a playback position is currently locked (inaccessible)
114 	bool IsPositionLocked(ORDERINDEX position) const;
115 	// Check if this sequence has subsongs separated by invalid ("---" or non-existing) patterns
116 	bool HasSubsongs() const;
117 #endif // MODPLUG_TRACKER
118 
119 	// Sequence name setter / getter
SetName(const mpt::ustring & newName)120 	inline void SetName(const mpt::ustring &newName) { m_name = newName;}
GetName()121 	inline mpt::ustring GetName() const { return m_name; }
122 
123 	// Restart position setter / getter
SetRestartPos(ORDERINDEX restartPos)124 	inline void SetRestartPos(ORDERINDEX restartPos) noexcept { m_restartPos = restartPos; }
GetRestartPos()125 	inline ORDERINDEX GetRestartPos() const noexcept { return m_restartPos; }
126 };
127 
128 
129 class ModSequenceSet
130 {
131 	friend void ReadModSequenceOld(std::istream& iStrm, ModSequenceSet& seq, const size_t);
132 	friend void ReadModSequences(std::istream& iStrm, ModSequenceSet& seq, const size_t, mpt::Charset defaultCharset);
133 
134 protected:
135 	std::vector<ModSequence> m_Sequences;  // Array of sequences
136 	CSoundFile &m_sndFile;
137 	SEQUENCEINDEX m_currentSeq = 0;  // Index of current sequence.
138 
139 public:
140 	ModSequenceSet(CSoundFile &sndFile);
141 	ModSequenceSet(ModSequenceSet &&) noexcept = default;
142 
143 	// Remove all sequences and initialize default sequence
144 	void Initialize();
145 
146 	// Get the working sequence
operator()147 	ModSequence& operator() () { return m_Sequences[m_currentSeq]; }
operator()148 	const ModSequence& operator() () const { return m_Sequences[m_currentSeq]; }
149 	// Get an arbitrary sequence
operator()150 	ModSequence& operator() (SEQUENCEINDEX seq) { return m_Sequences[seq]; }
operator()151 	const ModSequence& operator() (SEQUENCEINDEX seq) const { return m_Sequences[seq]; }
152 
GetNumSequences()153 	SEQUENCEINDEX GetNumSequences() const { return static_cast<SEQUENCEINDEX>(m_Sequences.size()); }
GetCurrentSequenceIndex()154 	SEQUENCEINDEX GetCurrentSequenceIndex() const { return m_currentSeq; }
155 
156 	// Sets working sequence.
157 	void SetSequence(SEQUENCEINDEX);
158 	// Add new empty sequence.
159 	// Returns the ID of the new sequence, or SEQUENCEINDEX_INVALID on failure.
160 	SEQUENCEINDEX AddSequence();
161 	// Removes given sequence.
162 	void RemoveSequence(SEQUENCEINDEX);
163 
164 	// Returns the internal representation of a stop '---' index
GetInvalidPatIndex()165 	static constexpr PATTERNINDEX GetInvalidPatIndex() { return ModSequence::GetInvalidPatIndex(); }
166 	// Returns the internal representation of an ignore '+++' index
GetIgnoreIndex()167 	static constexpr PATTERNINDEX GetIgnoreIndex() { return ModSequence::GetIgnoreIndex(); }
168 
169 #ifdef MODPLUG_TRACKER
170 	// Assigns a new set of sequences. The vector contents indicate which existing sequences to keep / duplicate or if a new sequences should be inserted (SEQUENCEINDEX_INVALID)
171 	// The function fails if the vector is empty or contains too many sequences.
172 	bool Rearrange(const std::vector<SEQUENCEINDEX> &newOrder);
173 
174 	// Adjust sequence when converting between module formats
175 	void OnModTypeChanged(MODTYPE oldType);
176 	// Check if there is a single sequences that qualifies for subsong splitting
177 	bool CanSplitSubsongs() const;
178 	// If there are subsongs (separated by "---" patterns) in the module,
179 	// asks user whether to convert these into multiple sequences (given that the
180 	// modformat supports multiple sequences).
181 	// Returns true if sequences were modified, false otherwise.
182 	bool SplitSubsongsToMultipleSequences();
183 
184 	// Convert the sequence's restart position information to a pattern command.
185 	bool RestartPosToPattern(SEQUENCEINDEX seq);
186 	// Merges multiple sequences into one and destroys all other sequences.
187 	// Returns false if there were no sequences to merge, true otherwise.
188 	bool MergeSequences();
189 #endif // MODPLUG_TRACKER
190 
begin()191 	std::vector<ModSequence>::iterator begin() { return m_Sequences.begin(); }
begin()192 	std::vector<ModSequence>::const_iterator begin() const { return m_Sequences.begin(); }
cbegin()193 	std::vector<ModSequence>::const_iterator cbegin() const { return m_Sequences.cbegin(); }
end()194 	std::vector<ModSequence>::iterator end() { return m_Sequences.end(); }
end()195 	std::vector<ModSequence>::const_iterator end() const { return m_Sequences.end(); }
cend()196 	std::vector<ModSequence>::const_iterator cend() const { return m_Sequences.cend(); }
197 };
198 
199 
200 const char FileIdSequences[] = "mptSeqC";
201 const char FileIdSequence[] = "mptSeq";
202 
203 #ifndef MODPLUG_NO_FILESAVE
204 void WriteModSequences(std::ostream& oStrm, const ModSequenceSet& seq);
205 #endif // MODPLUG_NO_FILESAVE
206 void ReadModSequences(std::istream& iStrm, ModSequenceSet& seq, const size_t nSize, mpt::Charset defaultCharset);
207 
208 #ifndef MODPLUG_NO_FILESAVE
209 void WriteModSequence(std::ostream& oStrm, const ModSequence& seq);
210 #endif // MODPLUG_NO_FILESAVE
211 void ReadModSequence(std::istream& iStrm, ModSequence& seq, const size_t, mpt::Charset defaultCharset);
212 
213 #ifndef MODPLUG_NO_FILESAVE
214 void WriteModSequenceOld(std::ostream& oStrm, const ModSequenceSet& seq);
215 #endif // MODPLUG_NO_FILESAVE
216 void ReadModSequenceOld(std::istream& iStrm, ModSequenceSet& seq, const size_t);
217 
218 
219 OPENMPT_NAMESPACE_END
220