1 /*
2  * ModSequence.cpp
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 #include "stdafx.h"
12 #include "ModSequence.h"
13 #include "Sndfile.h"
14 #include "mod_specifications.h"
15 #include "../common/version.h"
16 #include "../common/serialization_utils.h"
17 #include "mpt/io/io.hpp"
18 #include "mpt/io/io_stdstream.hpp"
19 
20 OPENMPT_NAMESPACE_BEGIN
21 
22 
ModSequence(CSoundFile & sndFile)23 ModSequence::ModSequence(CSoundFile &sndFile)
24 	: m_sndFile(sndFile)
25 {
26 }
27 
28 
operator =(const ModSequence & other)29 ModSequence& ModSequence::operator=(const ModSequence &other)
30 {
31 	MPT_ASSERT(&other.m_sndFile == &m_sndFile);
32 	if(&other == this)
33 		return *this;
34 	std::vector<PATTERNINDEX>::assign(other.begin(), other.end());
35 	m_name = other.m_name;
36 	m_restartPos = other.m_restartPos;
37 	return *this;
38 }
39 
40 
operator ==(const ModSequence & other) const41 bool ModSequence::operator== (const ModSequence &other) const
42 {
43 	return static_cast<const std::vector<PATTERNINDEX> &>(*this) == other
44 		&& m_name == other.m_name
45 		&& m_restartPos == other.m_restartPos;
46 }
47 
48 
NeedsExtraDatafield() const49 bool ModSequence::NeedsExtraDatafield() const
50 {
51 	return (m_sndFile.GetType() == MOD_TYPE_MPT && m_sndFile.Patterns.GetNumPatterns() > 0xFD);
52 }
53 
54 
AdjustToNewModType(const MODTYPE oldtype)55 void ModSequence::AdjustToNewModType(const MODTYPE oldtype)
56 {
57 	auto &specs = m_sndFile.GetModSpecifications();
58 
59 	if(oldtype != MOD_TYPE_NONE)
60 	{
61 		// If not supported, remove "+++" separator order items.
62 		if(!specs.hasIgnoreIndex)
63 		{
64 			RemovePattern(GetIgnoreIndex());
65 		}
66 		// If not supported, remove "---" items between patterns.
67 		if(!specs.hasStopIndex)
68 		{
69 			RemovePattern(GetInvalidPatIndex());
70 		}
71 	}
72 
73 	//Resize orderlist if needed.
74 	if(specs.ordersMax < size())
75 	{
76 		// Order list too long? Remove "unnecessary" order items first.
77 		if(oldtype != MOD_TYPE_NONE && specs.ordersMax < GetLengthTailTrimmed())
78 		{
79 			erase(std::remove_if(begin(), end(), [&] (PATTERNINDEX pat) { return !m_sndFile.Patterns.IsValidPat(pat); }), end());
80 			if(GetLengthTailTrimmed() > specs.ordersMax)
81 			{
82 				m_sndFile.AddToLog(LogWarning, U_("WARNING: Order list has been trimmed!"));
83 			}
84 		}
85 		resize(specs.ordersMax);
86 	}
87 }
88 
89 
GetLengthTailTrimmed() const90 ORDERINDEX ModSequence::GetLengthTailTrimmed() const
91 {
92 	if(empty())
93 		return 0;
94 	auto last = std::find_if(rbegin(), rend(), [] (PATTERNINDEX pat) { return pat != GetInvalidPatIndex(); });
95 	return static_cast<ORDERINDEX>(std::distance(begin(), last.base()));
96 }
97 
98 
GetLengthFirstEmpty() const99 ORDERINDEX ModSequence::GetLengthFirstEmpty() const
100 {
101 	return static_cast<ORDERINDEX>(std::distance(begin(), std::find(begin(), end(), GetInvalidPatIndex())));
102 }
103 
104 
GetNextOrderIgnoringSkips(const ORDERINDEX start) const105 ORDERINDEX ModSequence::GetNextOrderIgnoringSkips(const ORDERINDEX start) const
106 {
107 	if(empty())
108 		return 0;
109 	auto length = GetLength();
110 	ORDERINDEX next = std::min(ORDERINDEX(length - 1), ORDERINDEX(start + 1));
111 	while(next + 1 < length && at(next) == GetIgnoreIndex()) next++;
112 	return next;
113 }
114 
115 
GetPreviousOrderIgnoringSkips(const ORDERINDEX start) const116 ORDERINDEX ModSequence::GetPreviousOrderIgnoringSkips(const ORDERINDEX start) const
117 {
118 	const ORDERINDEX last = GetLastIndex();
119 	if(start == 0 || last == 0) return 0;
120 	ORDERINDEX prev = std::min(ORDERINDEX(start - 1), last);
121 	while(prev > 0 && at(prev) == GetIgnoreIndex()) prev--;
122 	return prev;
123 }
124 
125 
Remove(ORDERINDEX posBegin,ORDERINDEX posEnd)126 void ModSequence::Remove(ORDERINDEX posBegin, ORDERINDEX posEnd)
127 {
128 	if(posEnd < posBegin || posEnd >= size())
129 		return;
130 	erase(begin() + posBegin, begin() + posEnd + 1);
131 }
132 
133 
134 // Remove all references to a given pattern index from the order list. Jump commands are updated accordingly.
RemovePattern(PATTERNINDEX pat)135 void ModSequence::RemovePattern(PATTERNINDEX pat)
136 {
137 	// First, calculate the offset that needs to be applied to jump commands
138 	const ORDERINDEX orderLength = GetLengthTailTrimmed();
139 	std::vector<ORDERINDEX> newPosition(orderLength);
140 	ORDERINDEX maxJump = 0;
141 	for(ORDERINDEX i = 0; i < orderLength; i++)
142 	{
143 		newPosition[i] = i - maxJump;
144 		if(at(i) == pat)
145 		{
146 			maxJump++;
147 		}
148 	}
149 	if(!maxJump)
150 	{
151 		return;
152 	}
153 
154 	erase(std::remove(begin(), end(), pat), end());
155 
156 	// Only apply to patterns actually found in this sequence
157 	for(auto p : *this) if(m_sndFile.Patterns.IsValidPat(p))
158 	{
159 		for(auto &m : m_sndFile.Patterns[p])
160 		{
161 			if(m.command == CMD_POSITIONJUMP && m.param < newPosition.size())
162 			{
163 				m.param = static_cast<ModCommand::PARAM>(newPosition[m.param]);
164 			}
165 		}
166 	}
167 	if(m_restartPos < newPosition.size())
168 	{
169 		m_restartPos = newPosition[m_restartPos];
170 	}
171 }
172 
173 
assign(ORDERINDEX newSize,PATTERNINDEX pat)174 void ModSequence::assign(ORDERINDEX newSize, PATTERNINDEX pat)
175 {
176 	LimitMax(newSize, m_sndFile.GetModSpecifications().ordersMax);
177 	std::vector<PATTERNINDEX>::assign(newSize, pat);
178 }
179 
180 
insert(ORDERINDEX pos,ORDERINDEX count,PATTERNINDEX fill)181 ORDERINDEX ModSequence::insert(ORDERINDEX pos, ORDERINDEX count, PATTERNINDEX fill)
182 {
183 	const auto ordersMax = m_sndFile.GetModSpecifications().ordersMax;
184 	if(pos >= ordersMax || GetLengthTailTrimmed() >= ordersMax || count == 0)
185 		return 0;
186 	// Limit number of orders to be inserted so that we don't exceed the format limit.
187 	LimitMax(count, static_cast<ORDERINDEX>(ordersMax - pos));
188 	reserve(std::max(pos, GetLength()) + count);
189 	// Inserting past the end of the container?
190 	if(pos > size())
191 		resize(pos);
192 	std::vector<PATTERNINDEX>::insert(begin() + pos, count, fill);
193 	// Did we overgrow? Remove patterns at end.
194 	if(size() > ordersMax)
195 		resize(ordersMax);
196 	return count;
197 }
198 
199 
IsValidPat(ORDERINDEX ord) const200 bool ModSequence::IsValidPat(ORDERINDEX ord) const
201 {
202 	if(ord < size())
203 		return m_sndFile.Patterns.IsValidPat(at(ord));
204 	return false;
205 }
206 
207 
PatternAt(ORDERINDEX ord) const208 CPattern *ModSequence::PatternAt(ORDERINDEX ord) const
209 {
210 	if(!IsValidPat(ord))
211 		return nullptr;
212 	return &m_sndFile.Patterns[at(ord)];
213 }
214 
215 
FindOrder(PATTERNINDEX pat,ORDERINDEX startSearchAt,bool searchForward) const216 ORDERINDEX ModSequence::FindOrder(PATTERNINDEX pat, ORDERINDEX startSearchAt, bool searchForward) const
217 {
218 	const ORDERINDEX length = GetLength();
219 	if(startSearchAt >= length)
220 		return ORDERINDEX_INVALID;
221 	ORDERINDEX ord = startSearchAt;
222 	for(ORDERINDEX p = 0; p < length; p++)
223 	{
224 		if(at(ord) == pat)
225 		{
226 			return ord;
227 		}
228 		if(searchForward)
229 		{
230 			if(++ord >= length)
231 				ord = 0;
232 		} else
233 		{
234 			if(ord-- == 0)
235 				ord = length - 1;
236 		}
237 	}
238 	return ORDERINDEX_INVALID;
239 }
240 
241 
EnsureUnique(ORDERINDEX ord)242 PATTERNINDEX ModSequence::EnsureUnique(ORDERINDEX ord)
243 {
244 	PATTERNINDEX pat = at(ord);
245 	if(!IsValidPat(ord))
246 		return pat;
247 
248 	for(const auto &sequence : m_sndFile.Order)
249 	{
250 		ORDERINDEX ords = sequence.GetLength();
251 		for(ORDERINDEX o = 0; o < ords; o++)
252 		{
253 			if(sequence[o] == pat && (o != ord || &sequence != this))
254 			{
255 				// Found duplicate usage.
256 				PATTERNINDEX newPat = m_sndFile.Patterns.Duplicate(pat);
257 				if(newPat != PATTERNINDEX_INVALID)
258 				{
259 					at(ord) = newPat;
260 					return newPat;
261 				}
262 			}
263 		}
264 	}
265 	return pat;
266 }
267 
268 
269 /////////////////////////////////////
270 // ModSequenceSet
271 /////////////////////////////////////
272 
273 
ModSequenceSet(CSoundFile & sndFile)274 ModSequenceSet::ModSequenceSet(CSoundFile &sndFile)
275 	: m_sndFile(sndFile)
276 {
277 	Initialize();
278 }
279 
280 
Initialize()281 void ModSequenceSet::Initialize()
282 {
283 	m_currentSeq = 0;
284 	m_Sequences.assign(1, ModSequence(m_sndFile));
285 }
286 
287 
SetSequence(SEQUENCEINDEX n)288 void ModSequenceSet::SetSequence(SEQUENCEINDEX n)
289 {
290 	if(n < m_Sequences.size())
291 		m_currentSeq = n;
292 }
293 
294 
AddSequence()295 SEQUENCEINDEX ModSequenceSet::AddSequence()
296 {
297 	if(GetNumSequences() >= MAX_SEQUENCES)
298 		return SEQUENCEINDEX_INVALID;
299 	m_Sequences.push_back(ModSequence{m_sndFile});
300 	SetSequence(GetNumSequences() - 1);
301 	return GetNumSequences() - 1;
302 }
303 
304 
RemoveSequence(SEQUENCEINDEX i)305 void ModSequenceSet::RemoveSequence(SEQUENCEINDEX i)
306 {
307 	// Do nothing if index is invalid or if there's only one sequence left.
308 	if(i >= m_Sequences.size() || m_Sequences.size() <= 1)
309 		return;
310 	m_Sequences.erase(m_Sequences.begin() + i);
311 	if(i < m_currentSeq || m_currentSeq >= GetNumSequences())
312 		m_currentSeq--;
313 }
314 
315 
316 #ifdef MODPLUG_TRACKER
317 
Rearrange(const std::vector<SEQUENCEINDEX> & newOrder)318 bool ModSequenceSet::Rearrange(const std::vector<SEQUENCEINDEX> &newOrder)
319 {
320 	if(newOrder.empty() || newOrder.size() > MAX_SEQUENCES)
321 		return false;
322 
323 	const auto oldSequences = std::move(m_Sequences);
324 	m_Sequences.assign(newOrder.size(), ModSequence{m_sndFile});
325 	for(size_t i = 0; i < newOrder.size(); i++)
326 	{
327 		if(newOrder[i] < oldSequences.size())
328 			m_Sequences[i] = oldSequences[newOrder[i]];
329 	}
330 
331 	if(m_currentSeq > m_Sequences.size())
332 		m_currentSeq = GetNumSequences() - 1u;
333 	return true;
334 }
335 
336 
OnModTypeChanged(MODTYPE oldType)337 void ModSequenceSet::OnModTypeChanged(MODTYPE oldType)
338 {
339 	for(auto &seq : m_Sequences)
340 	{
341 		seq.AdjustToNewModType(oldType);
342 	}
343 	if(m_sndFile.GetModSpecifications(oldType).sequencesMax > 1 && m_sndFile.GetModSpecifications().sequencesMax <= 1)
344 		MergeSequences();
345 }
346 
347 
CanSplitSubsongs() const348 bool ModSequenceSet::CanSplitSubsongs() const
349 {
350 	return GetNumSequences() == 1 && m_sndFile.GetModSpecifications().sequencesMax > 1 && m_Sequences[0].HasSubsongs();
351 }
352 
353 
SplitSubsongsToMultipleSequences()354 bool ModSequenceSet::SplitSubsongsToMultipleSequences()
355 {
356 	if(!CanSplitSubsongs())
357 		return false;
358 
359 	bool modified = false;
360 	const ORDERINDEX length = m_Sequences[0].GetLengthTailTrimmed();
361 
362 	for(ORDERINDEX ord = 0; ord < length; ord++)
363 	{
364 		// End of subsong?
365 		if(!m_Sequences[0].IsValidPat(ord) && m_Sequences[0][ord] != GetIgnoreIndex())
366 		{
367 			// Remove all separator patterns between current and next subsong first
368 			while(ord < length && !m_sndFile.Patterns.IsValidPat(m_Sequences[0][ord]))
369 			{
370 				m_Sequences[0][ord] = GetInvalidPatIndex();
371 				ord++;
372 				modified = true;
373 			}
374 			if(ord >= length)
375 				break;
376 
377 			const SEQUENCEINDEX newSeq = AddSequence();
378 			if(newSeq == SEQUENCEINDEX_INVALID)
379 				break;
380 
381 			const ORDERINDEX startOrd = ord;
382 			m_Sequences[newSeq].reserve(length - startOrd);
383 			modified = true;
384 
385 			// Now, move all following orders to the new sequence
386 			while(ord < length && m_Sequences[0][ord] != GetInvalidPatIndex())
387 			{
388 				PATTERNINDEX copyPat = m_Sequences[0][ord];
389 				m_Sequences[newSeq].push_back(copyPat);
390 				m_Sequences[0][ord] = GetInvalidPatIndex();
391 				ord++;
392 
393 				// Is this a valid pattern? adjust pattern jump commands, if necessary.
394 				if(m_sndFile.Patterns.IsValidPat(copyPat))
395 				{
396 					for(auto &m : m_sndFile.Patterns[copyPat])
397 					{
398 						if(m.command == CMD_POSITIONJUMP && m.param >= startOrd)
399 						{
400 							m.param = static_cast<ModCommand::PARAM>(m.param - startOrd);
401 						}
402 					}
403 				}
404 			}
405 			ord--;
406 		}
407 	}
408 	SetSequence(0);
409 	return modified;
410 }
411 
412 
413 // Convert the sequence's restart position information to a pattern command.
RestartPosToPattern(SEQUENCEINDEX seq)414 bool ModSequenceSet::RestartPosToPattern(SEQUENCEINDEX seq)
415 {
416 	bool result = false;
417 	auto length = m_sndFile.GetLength(eNoAdjust, GetLengthTarget(true).StartPos(seq, 0, 0));
418 	ModSequence &order = m_Sequences[seq];
419 	for(const auto &subSong : length)
420 	{
421 		if(subSong.endOrder != ORDERINDEX_INVALID && subSong.endRow != ROWINDEX_INVALID)
422 		{
423 			if(mpt::in_range<ModCommand::PARAM>(order.GetRestartPos()))
424 			{
425 				PATTERNINDEX writePat = order.EnsureUnique(subSong.endOrder);
426 				result = m_sndFile.Patterns[writePat].WriteEffect(
427 					EffectWriter(CMD_POSITIONJUMP, static_cast<ModCommand::PARAM>(order.GetRestartPos())).Row(subSong.endRow).RetryNextRow());
428 			} else
429 			{
430 				result = false;
431 			}
432 		}
433 	}
434 	order.SetRestartPos(0);
435 	return result;
436 }
437 
438 
MergeSequences()439 bool ModSequenceSet::MergeSequences()
440 {
441 	if(GetNumSequences() <= 1)
442 		return false;
443 
444 	ModSequence &firstSeq = m_Sequences[0];
445 	firstSeq.resize(firstSeq.GetLengthTailTrimmed());
446 	std::vector<SEQUENCEINDEX> patternsFixed(m_sndFile.Patterns.Size(), SEQUENCEINDEX_INVALID); // pattern fixed by other sequence already?
447 	// Mark patterns handled in first sequence
448 	for(auto pat : firstSeq)
449 	{
450 		if(m_sndFile.Patterns.IsValidPat(pat))
451 			patternsFixed[pat] = 0;
452 	}
453 
454 	for(SEQUENCEINDEX seqNum = 1; seqNum < GetNumSequences(); seqNum++)
455 	{
456 		ModSequence &seq = m_Sequences[seqNum];
457 		const ORDERINDEX firstOrder = firstSeq.GetLength() + 1; // +1 for separator item
458 		const ORDERINDEX lengthTrimmed = seq.GetLengthTailTrimmed();
459 		if(firstOrder + lengthTrimmed > m_sndFile.GetModSpecifications().ordersMax)
460 		{
461 			m_sndFile.AddToLog(LogWarning, MPT_UFORMAT("WARNING: Cannot merge Sequence {} (too long!)")(seqNum + 1));
462 			continue;
463 		}
464 		firstSeq.reserve(firstOrder + lengthTrimmed);
465 		firstSeq.push_back(); // Separator item
466 		RestartPosToPattern(seqNum);
467 		for(ORDERINDEX ord = 0; ord < lengthTrimmed; ord++)
468 		{
469 			PATTERNINDEX pat = seq[ord];
470 			firstSeq.push_back(pat);
471 
472 			// Try to fix pattern jump commands
473 			if(!m_sndFile.Patterns.IsValidPat(pat)) continue;
474 
475 			auto m = m_sndFile.Patterns[pat].begin();
476 			for(size_t len = 0; len < m_sndFile.Patterns[pat].GetNumRows() * m_sndFile.m_nChannels; m++, len++)
477 			{
478 				if(m->command == CMD_POSITIONJUMP)
479 				{
480 					if(patternsFixed[pat] != SEQUENCEINDEX_INVALID && patternsFixed[pat] != seqNum)
481 					{
482 						// Oops, some other sequence uses this pattern already.
483 						const PATTERNINDEX newPat = m_sndFile.Patterns.Duplicate(pat, true);
484 						if(newPat != PATTERNINDEX_INVALID)
485 						{
486 							// Could create new pattern - copy data over and continue from here.
487 							firstSeq[firstOrder + ord] = newPat;
488 							m = m_sndFile.Patterns[newPat].begin() + len;
489 							if(newPat >= patternsFixed.size())
490 								patternsFixed.resize(newPat + 1, SEQUENCEINDEX_INVALID);
491 							pat = newPat;
492 						} else
493 						{
494 							// Cannot create new pattern: notify the user
495 							m_sndFile.AddToLog(LogWarning, MPT_UFORMAT("CONFLICT: Pattern break commands in Pattern {} might be broken since it has been used in several sequences!")(pat));
496 						}
497 					}
498 					m->param = static_cast<ModCommand::PARAM>(m->param + firstOrder);
499 					patternsFixed[pat] = seqNum;
500 				}
501 			}
502 		}
503 	}
504 	m_Sequences.erase(m_Sequences.begin() + 1, m_Sequences.end());
505 	return true;
506 }
507 
508 
509 // Check if a playback position is currently locked (inaccessible)
IsPositionLocked(ORDERINDEX position) const510 bool ModSequence::IsPositionLocked(ORDERINDEX position) const
511 {
512 	return(m_sndFile.m_lockOrderStart != ORDERINDEX_INVALID
513 		&& (position < m_sndFile.m_lockOrderStart || position > m_sndFile.m_lockOrderEnd));
514 }
515 
516 
HasSubsongs() const517 bool ModSequence::HasSubsongs() const
518 {
519 	const auto endPat = begin() + GetLengthTailTrimmed();
520 	return std::find_if(begin(), endPat,
521 		[&](PATTERNINDEX pat) { return pat != GetIgnoreIndex() && !m_sndFile.Patterns.IsValidPat(pat); }) != endPat;
522 }
523 #endif // MODPLUG_TRACKER
524 
525 
526 /////////////////////////////////////
527 // Read/Write
528 /////////////////////////////////////
529 
530 
531 #ifndef MODPLUG_NO_FILESAVE
WriteAsByte(std::ostream & f,const ORDERINDEX count,uint8 stopIndex,uint8 ignoreIndex) const532 size_t ModSequence::WriteAsByte(std::ostream &f, const ORDERINDEX count, uint8 stopIndex, uint8 ignoreIndex) const
533 {
534 	const size_t limit = std::min(count, GetLength());
535 
536 	for(size_t i = 0; i < limit; i++)
537 	{
538 		const PATTERNINDEX pat = at(i);
539 		uint8 temp = static_cast<uint8>(pat);
540 
541 		if(pat == GetInvalidPatIndex()) temp = stopIndex;
542 		else if(pat == GetIgnoreIndex() || pat > 0xFF) temp = ignoreIndex;
543 		mpt::IO::WriteIntLE<uint8>(f, temp);
544 	}
545 	// Fill non-existing order items with stop indices
546 	for(size_t i = limit; i < count; i++)
547 	{
548 		mpt::IO::WriteIntLE<uint8>(f, stopIndex);
549 	}
550 	return count; //Returns the number of bytes written.
551 }
552 #endif // MODPLUG_NO_FILESAVE
553 
554 
ReadModSequenceOld(std::istream & iStrm,ModSequenceSet & seq,const size_t)555 void ReadModSequenceOld(std::istream& iStrm, ModSequenceSet& seq, const size_t)
556 {
557 	uint16 size;
558 	mpt::IO::ReadIntLE<uint16>(iStrm, size);
559 	if(size > ModSpecs::mptm.ordersMax)
560 	{
561 		seq.m_sndFile.AddToLog(LogWarning, MPT_UFORMAT("Module has sequence of length {}; it will be truncated to maximum supported length, {}.")(size, ModSpecs::mptm.ordersMax));
562 		size = ModSpecs::mptm.ordersMax;
563 	}
564 	seq(0).resize(size);
565 	for(auto &pat : seq(0))
566 	{
567 		uint16 temp;
568 		mpt::IO::ReadIntLE<uint16>(iStrm, temp);
569 		pat = temp;
570 	}
571 }
572 
573 
574 #ifndef MODPLUG_NO_FILESAVE
WriteModSequenceOld(std::ostream & oStrm,const ModSequenceSet & seq)575 void WriteModSequenceOld(std::ostream& oStrm, const ModSequenceSet& seq)
576 {
577 	const uint16 size = seq().GetLength();
578 	mpt::IO::WriteIntLE<uint16>(oStrm, size);
579 	for(auto pat : seq())
580 	{
581 		mpt::IO::WriteIntLE<uint16>(oStrm, static_cast<uint16>(pat));
582 	}
583 }
584 #endif // MODPLUG_NO_FILESAVE
585 
586 
587 #ifndef MODPLUG_NO_FILESAVE
WriteModSequence(std::ostream & oStrm,const ModSequence & seq)588 void WriteModSequence(std::ostream& oStrm, const ModSequence& seq)
589 {
590 	srlztn::SsbWrite ssb(oStrm);
591 	ssb.BeginWrite(FileIdSequence, Version::Current().GetRawVersion());
592 	int8 useUTF8 = 1;
593 	ssb.WriteItem(useUTF8, "u");
594 	ssb.WriteItem(mpt::ToCharset(mpt::Charset::UTF8, seq.GetName()), "n");
595 	const uint16 length = seq.GetLengthTailTrimmed();
596 	ssb.WriteItem<uint16>(length, "l");
597 	ssb.WriteItem(seq, "a", srlztn::VectorWriter<uint16>(length));
598 	if(seq.GetRestartPos() > 0)
599 		ssb.WriteItem<uint16>(seq.GetRestartPos(), "r");
600 	ssb.FinishWrite();
601 }
602 #endif // MODPLUG_NO_FILESAVE
603 
604 
ReadModSequence(std::istream & iStrm,ModSequence & seq,const size_t,mpt::Charset defaultCharset)605 void ReadModSequence(std::istream& iStrm, ModSequence& seq, const size_t, mpt::Charset defaultCharset)
606 {
607 	srlztn::SsbRead ssb(iStrm);
608 	ssb.BeginRead(FileIdSequence, Version::Current().GetRawVersion());
609 	if ((ssb.GetStatus() & srlztn::SNT_FAILURE) != 0)
610 		return;
611 	int8 useUTF8 = 0;
612 	ssb.ReadItem(useUTF8, "u");
613 	std::string str;
614 	ssb.ReadItem(str, "n");
615 	seq.SetName(mpt::ToUnicode(useUTF8 ? mpt::Charset::UTF8 : defaultCharset, str));
616 	ORDERINDEX nSize = 0;
617 	ssb.ReadItem(nSize, "l");
618 	LimitMax(nSize, ModSpecs::mptm.ordersMax);
619 	ssb.ReadItem(seq, "a", srlztn::VectorReader<uint16>(nSize));
620 
621 	ORDERINDEX restartPos = ORDERINDEX_INVALID;
622 	if(ssb.ReadItem(restartPos, "r") != srlztn::SsbRead::EntryNotFound && restartPos < nSize)
623 		seq.SetRestartPos(restartPos);
624 }
625 
626 
627 #ifndef MODPLUG_NO_FILESAVE
WriteModSequences(std::ostream & oStrm,const ModSequenceSet & seq)628 void WriteModSequences(std::ostream& oStrm, const ModSequenceSet& seq)
629 {
630 	srlztn::SsbWrite ssb(oStrm);
631 	ssb.BeginWrite(FileIdSequences, Version::Current().GetRawVersion());
632 	const uint8 nSeqs = seq.GetNumSequences();
633 	const uint8 nCurrent = seq.GetCurrentSequenceIndex();
634 	ssb.WriteItem(nSeqs, "n");
635 	ssb.WriteItem(nCurrent, "c");
636 	for(uint8 i = 0; i < nSeqs; i++)
637 	{
638 		ssb.WriteItem(seq(i), srlztn::ID::FromInt<uint8>(i), &WriteModSequence);
639 	}
640 	ssb.FinishWrite();
641 }
642 #endif // MODPLUG_NO_FILESAVE
643 
644 
ReadModSequences(std::istream & iStrm,ModSequenceSet & seq,const size_t,mpt::Charset defaultCharset)645 void ReadModSequences(std::istream& iStrm, ModSequenceSet& seq, const size_t, mpt::Charset defaultCharset)
646 {
647 	srlztn::SsbRead ssb(iStrm);
648 	ssb.BeginRead(FileIdSequences, Version::Current().GetRawVersion());
649 	if ((ssb.GetStatus() & srlztn::SNT_FAILURE) != 0)
650 		return;
651 	SEQUENCEINDEX seqs = 0;
652 	uint8 currentSeq = 0;
653 	ssb.ReadItem(seqs, "n");
654 	if (seqs == 0)
655 		return;
656 	LimitMax(seqs, MAX_SEQUENCES);
657 	ssb.ReadItem(currentSeq, "c");
658 	if (seq.GetNumSequences() < seqs)
659 		seq.m_Sequences.resize(seqs, ModSequence(seq.m_sndFile));
660 
661 	// There used to be only one restart position for all sequences
662 	ORDERINDEX legacyRestartPos = seq(0).GetRestartPos();
663 
664 	for(SEQUENCEINDEX i = 0; i < seqs; i++)
665 	{
666 		seq(i).SetRestartPos(legacyRestartPos);
667 		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); });
668 	}
669 	seq.m_currentSeq = (currentSeq < seq.GetNumSequences()) ? currentSeq : 0;
670 }
671 
672 
673 OPENMPT_NAMESPACE_END
674