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