1 /*
2 * Pattern.cpp
3 * -----------
4 * Purpose: Module Pattern header class
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 "pattern.h"
13 #include "patternContainer.h"
14 #include "../common/serialization_utils.h"
15 #include "../common/version.h"
16 #include "ITTools.h"
17 #include "Sndfile.h"
18 #include "mod_specifications.h"
19 #include "mpt/io/io.hpp"
20 #include "mpt/io/io_stdstream.hpp"
21
22
23 OPENMPT_NAMESPACE_BEGIN
24
25
GetSoundFile()26 CSoundFile& CPattern::GetSoundFile() { return m_rPatternContainer.GetSoundFile(); }
GetSoundFile() const27 const CSoundFile& CPattern::GetSoundFile() const { return m_rPatternContainer.GetSoundFile(); }
28
29
GetNumChannels() const30 CHANNELINDEX CPattern::GetNumChannels() const
31 {
32 return GetSoundFile().GetNumChannels();
33 }
34
35
36 // Check if there is any note data on a given row.
IsEmptyRow(ROWINDEX row) const37 bool CPattern::IsEmptyRow(ROWINDEX row) const
38 {
39 if(m_ModCommands.empty() || !IsValidRow(row))
40 {
41 return true;
42 }
43
44 PatternRow data = GetRow(row);
45 for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++, data++)
46 {
47 if(!data->IsEmpty())
48 {
49 return false;
50 }
51 }
52 return true;
53 }
54
55
SetSignature(const ROWINDEX rowsPerBeat,const ROWINDEX rowsPerMeasure)56 bool CPattern::SetSignature(const ROWINDEX rowsPerBeat, const ROWINDEX rowsPerMeasure)
57 {
58 if(rowsPerBeat < 1
59 || rowsPerBeat > GetSoundFile().GetModSpecifications().patternRowsMax
60 || rowsPerMeasure < rowsPerBeat
61 || rowsPerMeasure > GetSoundFile().GetModSpecifications().patternRowsMax)
62 {
63 return false;
64 }
65 m_RowsPerBeat = rowsPerBeat;
66 m_RowsPerMeasure = rowsPerMeasure;
67 return true;
68 }
69
70
71 // Add or remove rows from the pattern.
Resize(const ROWINDEX newRowCount,bool enforceFormatLimits,bool resizeAtEnd)72 bool CPattern::Resize(const ROWINDEX newRowCount, bool enforceFormatLimits, bool resizeAtEnd)
73 {
74 CSoundFile &sndFile = GetSoundFile();
75
76 if(newRowCount == m_Rows || newRowCount < 1 || newRowCount > MAX_PATTERN_ROWS)
77 {
78 return false;
79 }
80 if(enforceFormatLimits)
81 {
82 auto &specs = sndFile.GetModSpecifications();
83 if(newRowCount > specs.patternRowsMax || newRowCount < specs.patternRowsMin) return false;
84 }
85
86 try
87 {
88 size_t count = ((newRowCount > m_Rows) ? (newRowCount - m_Rows) : (m_Rows - newRowCount)) * GetNumChannels();
89
90 if(newRowCount > m_Rows)
91 m_ModCommands.insert(resizeAtEnd ? m_ModCommands.end() : m_ModCommands.begin(), count, ModCommand::Empty());
92 else if(resizeAtEnd)
93 m_ModCommands.erase(m_ModCommands.end() - count, m_ModCommands.end());
94 else
95 m_ModCommands.erase(m_ModCommands.begin(), m_ModCommands.begin() + count);
96 } catch(mpt::out_of_memory e)
97 {
98 mpt::delete_out_of_memory(e);
99 return false;
100 }
101
102 m_Rows = newRowCount;
103 return true;
104 }
105
106
ClearCommands()107 void CPattern::ClearCommands()
108 {
109 std::fill(m_ModCommands.begin(), m_ModCommands.end(), ModCommand::Empty());
110 }
111
112
AllocatePattern(ROWINDEX rows)113 bool CPattern::AllocatePattern(ROWINDEX rows)
114 {
115 size_t newSize = GetNumChannels() * rows;
116 if(rows == 0)
117 {
118 return false;
119 } else if(rows == GetNumRows() && m_ModCommands.size() == newSize)
120 {
121 // Re-use allocated memory
122 ClearCommands();
123 return true;
124 } else
125 {
126 // Do this in two steps in order to keep the old pattern data in case of OOM
127 decltype(m_ModCommands) newPattern(newSize, ModCommand::Empty());
128 m_ModCommands = std::move(newPattern);
129 }
130 m_Rows = rows;
131 return true;
132 }
133
134
Deallocate()135 void CPattern::Deallocate()
136 {
137 m_Rows = m_RowsPerBeat = m_RowsPerMeasure = 0;
138 m_ModCommands.clear();
139 m_PatternName.clear();
140 }
141
142
operator =(const CPattern & pat)143 CPattern& CPattern::operator= (const CPattern &pat)
144 {
145 m_ModCommands = pat.m_ModCommands;
146 m_Rows = pat.m_Rows;
147 m_RowsPerBeat = pat.m_RowsPerBeat;
148 m_RowsPerMeasure = pat.m_RowsPerMeasure;
149 m_tempoSwing = pat.m_tempoSwing;
150 m_PatternName = pat.m_PatternName;
151 return *this;
152 }
153
154
155
operator ==(const CPattern & other) const156 bool CPattern::operator== (const CPattern &other) const
157 {
158 return GetNumRows() == other.GetNumRows()
159 && GetNumChannels() == other.GetNumChannels()
160 && GetOverrideSignature() == other.GetOverrideSignature()
161 && GetRowsPerBeat() == other.GetRowsPerBeat()
162 && GetRowsPerMeasure() == other.GetRowsPerMeasure()
163 && GetTempoSwing() == other.GetTempoSwing()
164 && m_ModCommands == other.m_ModCommands;
165 }
166
167
168 #ifdef MODPLUG_TRACKER
169
Expand()170 bool CPattern::Expand()
171 {
172 const ROWINDEX newRows = m_Rows * 2;
173 const CHANNELINDEX nChns = GetNumChannels();
174
175 if(m_ModCommands.empty()
176 || newRows > GetSoundFile().GetModSpecifications().patternRowsMax)
177 {
178 return false;
179 }
180
181 decltype(m_ModCommands) newPattern;
182 try
183 {
184 newPattern.assign(m_ModCommands.size() * 2, ModCommand::Empty());
185 } catch(mpt::out_of_memory e)
186 {
187 mpt::delete_out_of_memory(e);
188 return false;
189 }
190
191 for(auto mSrc = m_ModCommands.begin(), mDst = newPattern.begin(); mSrc != m_ModCommands.end(); mSrc += nChns, mDst += 2 * nChns)
192 {
193 std::copy(mSrc, mSrc + nChns, mDst);
194 }
195
196 m_ModCommands = std::move(newPattern);
197 m_Rows = newRows;
198
199 return true;
200 }
201
202
Shrink()203 bool CPattern::Shrink()
204 {
205 if (m_ModCommands.empty()
206 || m_Rows < GetSoundFile().GetModSpecifications().patternRowsMin * 2)
207 {
208 return false;
209 }
210
211 m_Rows /= 2;
212 const CHANNELINDEX nChns = GetNumChannels();
213
214 for(ROWINDEX y = 0; y < m_Rows; y++)
215 {
216 const PatternRow srcRow = GetRow(y * 2);
217 const PatternRow nextSrcRow = GetRow(y * 2 + 1);
218 PatternRow destRow = GetRow(y);
219
220 for(CHANNELINDEX x = 0; x < nChns; x++)
221 {
222 const ModCommand &src = srcRow[x];
223 const ModCommand &srcNext = nextSrcRow[x];
224 ModCommand &dest = destRow[x];
225 dest = src;
226
227 if(dest.note == NOTE_NONE && !dest.instr)
228 {
229 // Fill in data from next row if field is empty
230 dest.note = srcNext.note;
231 dest.instr = srcNext.instr;
232 if(srcNext.volcmd != VOLCMD_NONE)
233 {
234 dest.volcmd = srcNext.volcmd;
235 dest.vol = srcNext.vol;
236 }
237 if(dest.command == CMD_NONE)
238 {
239 dest.command = srcNext.command;
240 dest.param = srcNext.param;
241 }
242 }
243 }
244 }
245 m_ModCommands.resize(m_Rows * nChns);
246
247 return true;
248 }
249
250
251 #endif // MODPLUG_TRACKER
252
253
SetName(const std::string & newName)254 bool CPattern::SetName(const std::string &newName)
255 {
256 m_PatternName = newName;
257 return true;
258 }
259
260
SetName(const char * newName,size_t maxChars)261 bool CPattern::SetName(const char *newName, size_t maxChars)
262 {
263 if(newName == nullptr || maxChars == 0)
264 {
265 return false;
266 }
267 const auto nameEnd = std::find(newName, newName + maxChars, '\0');
268 m_PatternName.assign(newName, nameEnd);
269 return true;
270 }
271
272
273 // Write some kind of effect data to the pattern. Exact data to be written and write behaviour can be found in the EffectWriter object.
WriteEffect(EffectWriter & settings)274 bool CPattern::WriteEffect(EffectWriter &settings)
275 {
276 // First, reject invalid parameters.
277 if(m_ModCommands.empty()
278 || settings.m_row >= GetNumRows()
279 || (settings.m_channel >= GetNumChannels() && settings.m_channel != CHANNELINDEX_INVALID))
280 {
281 return false;
282 }
283
284 CHANNELINDEX scanChnMin = settings.m_channel, scanChnMax = settings.m_channel;
285
286 // Scan all channels
287 if(settings.m_channel == CHANNELINDEX_INVALID)
288 {
289 scanChnMin = 0;
290 scanChnMax = GetNumChannels() - 1;
291 }
292
293 ModCommand * const baseCommand = GetpModCommand(settings.m_row, scanChnMin);
294 ModCommand *m;
295
296 // Scan channel(s) for same effect type - if an effect of the same type is already present, exit.
297 if(!settings.m_allowMultiple)
298 {
299 m = baseCommand;
300 for(CHANNELINDEX i = scanChnMin; i <= scanChnMax; i++, m++)
301 {
302 if(!settings.m_isVolEffect && m->command == settings.m_command)
303 return true;
304 if(settings.m_isVolEffect && m->volcmd == settings.m_volcmd)
305 return true;
306 }
307 }
308
309 // Easy case: check if there's some space left to put the effect somewhere
310 m = baseCommand;
311 for(CHANNELINDEX i = scanChnMin; i <= scanChnMax; i++, m++)
312 {
313 if(!settings.m_isVolEffect && m->command == CMD_NONE)
314 {
315 m->command = settings.m_command;
316 m->param = settings.m_param;
317 return true;
318 }
319 if(settings.m_isVolEffect && m->volcmd == VOLCMD_NONE)
320 {
321 m->volcmd = settings.m_volcmd;
322 m->vol = settings.m_vol;
323 return true;
324 }
325 }
326
327 // Ok, apparently there's no space. If we haven't tried already, try to map it to the volume column or effect column instead.
328 if(settings.m_retry)
329 {
330 const bool isS3M = (GetSoundFile().GetType() & MOD_TYPE_S3M);
331
332 // Move some effects that also work in the volume column, so there's place for our new effect.
333 if(!settings.m_isVolEffect)
334 {
335 m = baseCommand;
336 for(CHANNELINDEX i = scanChnMin; i <= scanChnMax; i++, m++)
337 {
338 switch(m->command)
339 {
340 case CMD_VOLUME:
341 if(!GetSoundFile().GetModSpecifications().HasVolCommand(VOLCMD_VOLUME))
342 {
343 break;
344 }
345 m->volcmd = VOLCMD_VOLUME;
346 m->vol = m->param;
347 m->command = settings.m_command;
348 m->param = settings.m_param;
349 return true;
350
351 case CMD_PANNING8:
352 if(isS3M && m->param > 0x80)
353 {
354 break;
355 }
356
357 m->volcmd = VOLCMD_PANNING;
358 m->command = settings.m_command;
359
360 if(isS3M)
361 m->vol = (m->param + 1u) / 2u;
362 else
363 m->vol = (m->param + 2u) / 4u;
364
365 m->param = settings.m_param;
366 return true;
367
368 default:
369 break;
370 }
371 }
372 }
373
374 // Let's try it again by writing into the "other" effect column.
375 if(settings.m_isVolEffect)
376 {
377 // Convert volume effect to normal effect
378 ModCommand::COMMAND newCommand = CMD_NONE;
379 ModCommand::PARAM newParam = settings.m_vol;
380 switch(settings.m_volcmd)
381 {
382 case VOLCMD_PANNING:
383 newCommand = CMD_PANNING8;
384 newParam = mpt::saturate_cast<ModCommand::PARAM>(settings.m_vol * (isS3M ? 2u : 4u));
385 break;
386 case VOLCMD_VOLUME:
387 newCommand = CMD_VOLUME;
388 break;
389 default:
390 break;
391 }
392
393 if(newCommand != CMD_NONE)
394 {
395 settings.m_command = static_cast<EffectCommand>(newCommand);
396 settings.m_param = newParam;
397 settings.m_retry = false;
398 }
399 } else
400 {
401 // Convert normal effect to volume effect
402 ModCommand::VOLCMD newVolCmd = VOLCMD_NONE;
403 ModCommand::VOL newVol = settings.m_param;
404 if(settings.m_command == CMD_PANNING8 && isS3M)
405 {
406 // This needs some manual fixing.
407 if(settings.m_param <= 0x80)
408 {
409 // Can't have surround in volume column, only normal panning
410 newVolCmd = VOLCMD_PANNING;
411 newVol /= 2u;
412 }
413 } else
414 {
415 newVolCmd = settings.m_command;
416 if(!ModCommand::ConvertVolEffect(newVolCmd, newVol, true))
417 {
418 // No Success :(
419 newVolCmd = VOLCMD_NONE;
420 }
421 }
422
423 if(newVolCmd != CMD_NONE)
424 {
425 settings.m_volcmd = static_cast<VolumeCommand>(newVolCmd);
426 settings.m_vol = newVol;
427 settings.m_retry = false;
428 }
429 }
430
431 if(!settings.m_retry)
432 {
433 settings.m_isVolEffect = !settings.m_isVolEffect;
434 if(WriteEffect(settings))
435 {
436 return true;
437 }
438 }
439 }
440
441 // Try in the next row if possible (this may also happen if we already retried)
442 if(settings.m_retryMode == EffectWriter::rmTryNextRow && settings.m_row + 1 < GetNumRows())
443 {
444 settings.m_row++;
445 settings.m_retry = true;
446 return WriteEffect(settings);
447 } else if(settings.m_retryMode == EffectWriter::rmTryPreviousRow && settings.m_row > 0)
448 {
449 settings.m_row--;
450 settings.m_retry = true;
451 return WriteEffect(settings);
452 }
453
454 return false;
455 }
456
457
458 ////////////////////////////////////////////////////////////////////////
459 //
460 // Pattern serialization functions
461 //
462 ////////////////////////////////////////////////////////////////////////
463
464
465 enum maskbits
466 {
467 noteBit = (1 << 0),
468 instrBit = (1 << 1),
469 volcmdBit = (1 << 2),
470 volBit = (1 << 3),
471 commandBit = (1 << 4),
472 effectParamBit = (1 << 5),
473 extraData = (1 << 6)
474 };
475
476 void WriteData(std::ostream& oStrm, const CPattern& pat);
477 void ReadData(std::istream& iStrm, CPattern& pat, const size_t nSize = 0);
478
WriteModPattern(std::ostream & oStrm,const CPattern & pat)479 void WriteModPattern(std::ostream& oStrm, const CPattern& pat)
480 {
481 srlztn::SsbWrite ssb(oStrm);
482 ssb.BeginWrite(FileIdPattern, Version::Current().GetRawVersion());
483 ssb.WriteItem(pat, "data", &WriteData);
484 // pattern time signature
485 if(pat.GetOverrideSignature())
486 {
487 ssb.WriteItem<uint32>(pat.GetRowsPerBeat(), "RPB.");
488 ssb.WriteItem<uint32>(pat.GetRowsPerMeasure(), "RPM.");
489 }
490 if(pat.HasTempoSwing())
491 {
492 ssb.WriteItem<TempoSwing>(pat.GetTempoSwing(), "SWNG", TempoSwing::Serialize);
493 }
494 ssb.FinishWrite();
495 }
496
497
ReadModPattern(std::istream & iStrm,CPattern & pat,const size_t)498 void ReadModPattern(std::istream& iStrm, CPattern& pat, const size_t)
499 {
500 srlztn::SsbRead ssb(iStrm);
501 ssb.BeginRead(FileIdPattern, Version::Current().GetRawVersion());
502 if ((ssb.GetStatus() & srlztn::SNT_FAILURE) != 0)
503 return;
504 ssb.ReadItem(pat, "data", &ReadData);
505 // pattern time signature
506 uint32 rpb = 0, rpm = 0;
507 ssb.ReadItem<uint32>(rpb, "RPB.");
508 ssb.ReadItem<uint32>(rpm, "RPM.");
509 pat.SetSignature(rpb, rpm);
510 TempoSwing swing;
511 ssb.ReadItem<TempoSwing>(swing, "SWNG", TempoSwing::Deserialize);
512 if(!swing.empty())
513 swing.resize(pat.GetRowsPerBeat());
514 pat.SetTempoSwing(swing);
515 }
516
517
CreateDiffMask(const ModCommand & chnMC,const ModCommand & newMC)518 static uint8 CreateDiffMask(const ModCommand &chnMC, const ModCommand &newMC)
519 {
520 uint8 mask = 0;
521 if(chnMC.note != newMC.note)
522 mask |= noteBit;
523 if(chnMC.instr != newMC.instr)
524 mask |= instrBit;
525 if(chnMC.volcmd != newMC.volcmd)
526 mask |= volcmdBit;
527 if(chnMC.vol != newMC.vol)
528 mask |= volBit;
529 if(chnMC.command != newMC.command)
530 mask |= commandBit;
531 if(chnMC.param != newMC.param)
532 mask |= effectParamBit;
533 return mask;
534 }
535
536
537 // Writes pattern data. Adapted from SaveIT.
WriteData(std::ostream & oStrm,const CPattern & pat)538 void WriteData(std::ostream& oStrm, const CPattern& pat)
539 {
540 if(!pat.IsValid())
541 return;
542
543 const ROWINDEX rows = pat.GetNumRows();
544 const CHANNELINDEX chns = pat.GetNumChannels();
545 std::vector<ModCommand> lastChnMC(chns);
546
547 for(ROWINDEX r = 0; r<rows; r++)
548 {
549 for(CHANNELINDEX c = 0; c<chns; c++)
550 {
551 const ModCommand m = *pat.GetpModCommand(r, c);
552 // Writing only commands not written in IT-pattern writing:
553 // For now this means only NOTE_PC and NOTE_PCS.
554 if(!m.IsPcNote())
555 continue;
556 uint8 diffmask = CreateDiffMask(lastChnMC[c], m);
557 uint8 chval = static_cast<uint8>(c+1);
558 if(diffmask != 0)
559 chval |= IT_bitmask_patternChanEnabled_c;
560
561 mpt::IO::WriteIntLE<uint8>(oStrm, chval);
562
563 if(diffmask)
564 {
565 lastChnMC[c] = m;
566 mpt::IO::WriteIntLE<uint8>(oStrm, diffmask);
567 if(diffmask & noteBit) mpt::IO::WriteIntLE<uint8>(oStrm, m.note);
568 if(diffmask & instrBit) mpt::IO::WriteIntLE<uint8>(oStrm, m.instr);
569 if(diffmask & volcmdBit) mpt::IO::WriteIntLE<uint8>(oStrm, m.volcmd);
570 if(diffmask & volBit) mpt::IO::WriteIntLE<uint8>(oStrm, m.vol);
571 if(diffmask & commandBit) mpt::IO::WriteIntLE<uint8>(oStrm, m.command);
572 if(diffmask & effectParamBit) mpt::IO::WriteIntLE<uint8>(oStrm, m.param);
573 }
574 }
575 mpt::IO::WriteIntLE<uint8>(oStrm, 0); // Write end of row marker.
576 }
577 }
578
579
580 #define READITEM(itembit,id) \
581 if(diffmask & itembit) \
582 { \
583 mpt::IO::ReadIntLE<uint8>(iStrm, temp); \
584 if(ch < chns) \
585 lastChnMC[ch].id = temp; \
586 } \
587 if(ch < chns) \
588 m.id = lastChnMC[ch].id;
589
590
ReadData(std::istream & iStrm,CPattern & pat,const size_t)591 void ReadData(std::istream& iStrm, CPattern& pat, const size_t)
592 {
593 if (!pat.IsValid()) // Expecting patterns to be allocated and resized properly.
594 return;
595
596 const CHANNELINDEX chns = pat.GetNumChannels();
597 const ROWINDEX rows = pat.GetNumRows();
598
599 std::vector<ModCommand> lastChnMC(chns);
600
601 ROWINDEX row = 0;
602 while(row < rows && iStrm.good())
603 {
604 uint8 t = 0;
605 mpt::IO::ReadIntLE<uint8>(iStrm, t);
606 if(t == 0)
607 {
608 row++;
609 continue;
610 }
611
612 CHANNELINDEX ch = (t & IT_bitmask_patternChanField_c);
613 if(ch > 0)
614 ch--;
615
616 uint8 diffmask = 0;
617 if((t & IT_bitmask_patternChanEnabled_c) != 0)
618 mpt::IO::ReadIntLE<uint8>(iStrm, diffmask);
619 uint8 temp = 0;
620
621 ModCommand dummy = ModCommand::Empty();
622 ModCommand& m = (ch < chns) ? *pat.GetpModCommand(row, ch) : dummy;
623
624 READITEM(noteBit, note);
625 READITEM(instrBit, instr);
626 READITEM(volcmdBit, volcmd);
627 READITEM(volBit, vol);
628 READITEM(commandBit, command);
629 READITEM(effectParamBit, param);
630 if(diffmask & extraData)
631 {
632 //Ignore additional data.
633 uint8 size;
634 mpt::IO::ReadIntLE<uint8>(iStrm, size);
635 iStrm.ignore(size);
636 }
637 }
638 }
639
640 #undef READITEM
641
642
643 OPENMPT_NAMESPACE_END
644