1 /*
2 * Load_it.cpp
3 * -----------
4 * Purpose: IT (Impulse Tracker) module loader / saver
5 * Notes : Also handles MPTM loading / saving, as the formats are almost identical.
6 * Authors: Olivier Lapicque
7 * OpenMPT Devs
8 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
9 */
10
11
12 #include "stdafx.h"
13 #include "Loaders.h"
14 #include "tuningcollection.h"
15 #include "mod_specifications.h"
16 #ifdef MODPLUG_TRACKER
17 #include "../mptrack/Moddoc.h"
18 #include "../mptrack/TrackerSettings.h"
19 #endif // MODPLUG_TRACKER
20 #ifdef MPT_EXTERNAL_SAMPLES
21 #include "../common/mptPathString.h"
22 #endif // MPT_EXTERNAL_SAMPLES
23 #include "../common/serialization_utils.h"
24 #ifndef MODPLUG_NO_FILESAVE
25 #include "../common/mptFileIO.h"
26 #endif // MODPLUG_NO_FILESAVE
27 #include "plugins/PlugInterface.h"
28 #include <sstream>
29 #include "../common/version.h"
30 #include "ITTools.h"
31 #include "mpt/io/base.hpp"
32 #include "mpt/io/io.hpp"
33 #include "mpt/io/io_stdstream.hpp"
34
35
36 OPENMPT_NAMESPACE_BEGIN
37
38
39 const uint16 verMptFileVer = 0x891;
40 const uint16 verMptFileVerLoadLimit = 0x1000; // If cwtv-field is greater or equal to this value,
41 // the MPTM file will not be loaded.
42
43 /*
44 MPTM version history for cwtv-field in "IT" header (only for MPTM files!):
45 0x890(1.18.02.00) -> 0x891(1.19.00.00): Pattern-specific time signatures
46 Fixed behaviour of Pattern Loop command for rows > 255 (r617)
47 0x88F(1.18.01.00) -> 0x890(1.18.02.00): Removed volume command velocity :xy, added delay-cut command :xy.
48 0x88E(1.17.02.50) -> 0x88F(1.18.01.00): Numerous changes
49 0x88D(1.17.02.49) -> 0x88E(1.17.02.50): Changed ID to that of IT and undone the orderlist change done in
50 0x88A->0x88B. Now extended orderlist is saved as extension.
51 0x88C(1.17.02.48) -> 0x88D(1.17.02.49): Some tuning related changes - that part fails to read on older versions.
52 0x88B -> 0x88C: Changed type in which tuning number is printed to file: size_t -> uint16.
53 0x88A -> 0x88B: Changed order-to-pattern-index table type from uint8-array to vector<uint32>.
54 */
55
56
57 #ifndef MODPLUG_NO_FILESAVE
58
AreNonDefaultTuningsUsed(const CSoundFile & sf)59 static bool AreNonDefaultTuningsUsed(const CSoundFile& sf)
60 {
61 const INSTRUMENTINDEX numIns = sf.GetNumInstruments();
62 for(INSTRUMENTINDEX i = 1; i <= numIns; i++)
63 {
64 if(sf.Instruments[i] != nullptr && sf.Instruments[i]->pTuning != nullptr)
65 return true;
66 }
67 return false;
68 }
69
WriteTuningCollection(std::ostream & oStrm,const CTuningCollection & tc)70 static void WriteTuningCollection(std::ostream& oStrm, const CTuningCollection& tc)
71 {
72 tc.Serialize(oStrm, U_("Tune specific tunings"));
73 }
74
WriteTuningMap(std::ostream & oStrm,const CSoundFile & sf)75 static void WriteTuningMap(std::ostream& oStrm, const CSoundFile& sf)
76 {
77 if(sf.GetNumInstruments() > 0)
78 {
79 //Writing instrument tuning data: first creating
80 //tuning name <-> tuning id number map,
81 //and then writing the tuning id for every instrument.
82 //For example if there are 6 instruments and
83 //first half use tuning 'T1', and the other half
84 //tuning 'T2', the output would be something like
85 //T1 1 T2 2 1 1 1 2 2 2
86
87 //Creating the tuning address <-> tuning id number map.
88 std::map<CTuning*, uint16> tNameToShort_Map;
89
90 unsigned short figMap = 0;
91 for(INSTRUMENTINDEX i = 1; i <= sf.GetNumInstruments(); i++)
92 {
93 CTuning *pTuning = nullptr;
94 if(sf.Instruments[i] != nullptr)
95 {
96 pTuning = sf.Instruments[i]->pTuning;
97 }
98 auto iter = tNameToShort_Map.find(pTuning);
99 if(iter != tNameToShort_Map.end())
100 continue; //Tuning already mapped.
101
102 tNameToShort_Map[pTuning] = figMap;
103 figMap++;
104 }
105
106 //...and write the map with tuning names replacing
107 //the addresses.
108 const uint16 tuningMapSize = static_cast<uint16>(tNameToShort_Map.size());
109 mpt::IO::WriteIntLE<uint16>(oStrm, tuningMapSize);
110 for(auto &iter : tNameToShort_Map)
111 {
112 if(iter.first)
113 mpt::IO::WriteSizedStringLE<uint8>(oStrm, mpt::ToCharset(mpt::Charset::UTF8, iter.first->GetName()));
114 else //Case: Using original IT tuning.
115 mpt::IO::WriteSizedStringLE<uint8>(oStrm, "->MPT_ORIGINAL_IT<-");
116
117 mpt::IO::WriteIntLE<uint16>(oStrm, iter.second);
118 }
119
120 //Writing tuning data for instruments.
121 for(INSTRUMENTINDEX i = 1; i <= sf.GetNumInstruments(); i++)
122 {
123 CTuning *pTuning = nullptr;
124 if(sf.Instruments[i] != nullptr)
125 {
126 pTuning = sf.Instruments[i]->pTuning;
127 }
128 auto iter = tNameToShort_Map.find(pTuning);
129 if(iter == tNameToShort_Map.end()) //Should never happen
130 {
131 sf.AddToLog(LogError, U_("Error: 210807_1"));
132 return;
133 }
134 mpt::IO::WriteIntLE<uint16>(oStrm, iter->second);
135 }
136 }
137 }
138
139 #endif // MODPLUG_NO_FILESAVE
140
141
ReadTuningCollection(std::istream & iStrm,CTuningCollection & tc,const std::size_t dummy,mpt::Charset defaultCharset)142 static void ReadTuningCollection(std::istream &iStrm, CTuningCollection &tc, const std::size_t dummy, mpt::Charset defaultCharset)
143 {
144 MPT_UNREFERENCED_PARAMETER(dummy);
145 mpt::ustring name;
146 tc.Deserialize(iStrm, name, defaultCharset);
147 }
148
149
150 template<class TUNNUMTYPE, class STRSIZETYPE>
ReadTuningMapTemplate(std::istream & iStrm,std::map<uint16,mpt::ustring> & shortToTNameMap,mpt::Charset charset,const size_t maxNum=500)151 static bool ReadTuningMapTemplate(std::istream& iStrm, std::map<uint16, mpt::ustring> &shortToTNameMap, mpt::Charset charset, const size_t maxNum = 500)
152 {
153 TUNNUMTYPE numTuning = 0;
154 mpt::IO::ReadIntLE<TUNNUMTYPE>(iStrm, numTuning);
155 if(numTuning > maxNum)
156 return true;
157
158 for(size_t i = 0; i < numTuning; i++)
159 {
160 std::string temp;
161 uint16 ui = 0;
162 if(!mpt::IO::ReadSizedStringLE<STRSIZETYPE>(iStrm, temp, 255))
163 return true;
164
165 mpt::IO::ReadIntLE<uint16>(iStrm, ui);
166 shortToTNameMap[ui] = mpt::ToUnicode(charset, temp);
167 }
168 if(iStrm.good())
169 return false;
170 else
171 return true;
172 }
173
174
ReadTuningMapImpl(std::istream & iStrm,CSoundFile & csf,mpt::Charset charset,const size_t=0,bool old=false)175 static void ReadTuningMapImpl(std::istream& iStrm, CSoundFile& csf, mpt::Charset charset, const size_t = 0, bool old = false)
176 {
177 std::map<uint16, mpt::ustring> shortToTNameMap;
178 if(old)
179 {
180 ReadTuningMapTemplate<uint32, uint32>(iStrm, shortToTNameMap, charset);
181 } else
182 {
183 ReadTuningMapTemplate<uint16, uint8>(iStrm, shortToTNameMap, charset);
184 }
185
186 // Read & set tunings for instruments
187 std::vector<mpt::ustring> notFoundTunings;
188 for(INSTRUMENTINDEX i = 1; i<=csf.GetNumInstruments(); i++)
189 {
190 uint16 ui = 0;
191 mpt::IO::ReadIntLE<uint16>(iStrm, ui);
192 auto iter = shortToTNameMap.find(ui);
193 if(csf.Instruments[i] && iter != shortToTNameMap.end())
194 {
195 const mpt::ustring str = iter->second;
196
197 if(str == U_("->MPT_ORIGINAL_IT<-"))
198 {
199 csf.Instruments[i]->pTuning = nullptr;
200 continue;
201 }
202
203 csf.Instruments[i]->pTuning = csf.GetTuneSpecificTunings().GetTuning(str);
204 if(csf.Instruments[i]->pTuning)
205 continue;
206
207 #ifdef MODPLUG_TRACKER
208 CTuning *localTuning = TrackerSettings::Instance().oldLocalTunings->GetTuning(str);
209 if(localTuning)
210 {
211 std::unique_ptr<CTuning> pNewTuning = std::unique_ptr<CTuning>(new CTuning(*localTuning));
212 CTuning *pT = csf.GetTuneSpecificTunings().AddTuning(std::move(pNewTuning));
213 if(pT)
214 {
215 csf.AddToLog(LogInformation, U_("Local tunings are deprecated and no longer supported. Tuning '") + str + U_("' found in Local tunings has been copied to Tune-specific tunings and will be saved in the module file."));
216 csf.Instruments[i]->pTuning = pT;
217 if(csf.GetpModDoc() != nullptr)
218 {
219 csf.GetpModDoc()->SetModified();
220 }
221 continue;
222 } else
223 {
224 csf.AddToLog(LogError, U_("Copying Local tuning '") + str + U_("' to Tune-specific tunings failed."));
225 }
226 }
227 #endif
228
229 if(str == U_("12TET [[fs15 1.17.02.49]]") || str == U_("12TET"))
230 {
231 std::unique_ptr<CTuning> pNewTuning = csf.CreateTuning12TET(str);
232 CTuning *pT = csf.GetTuneSpecificTunings().AddTuning(std::move(pNewTuning));
233 if(pT)
234 {
235 #ifdef MODPLUG_TRACKER
236 csf.AddToLog(LogInformation, U_("Built-in tunings will no longer be used. Tuning '") + str + U_("' has been copied to Tune-specific tunings and will be saved in the module file."));
237 csf.Instruments[i]->pTuning = pT;
238 if(csf.GetpModDoc() != nullptr)
239 {
240 csf.GetpModDoc()->SetModified();
241 }
242 #endif
243 continue;
244 } else
245 {
246 #ifdef MODPLUG_TRACKER
247 csf.AddToLog(LogError, U_("Copying Built-in tuning '") + str + U_("' to Tune-specific tunings failed."));
248 #endif
249 }
250 }
251
252 // Checking if not found tuning already noticed.
253 if(!mpt::contains(notFoundTunings, str))
254 {
255 notFoundTunings.push_back(str);
256 csf.AddToLog(LogWarning, U_("Tuning '") + str + U_("' used by the module was not found."));
257 #ifdef MODPLUG_TRACKER
258 if(csf.GetpModDoc() != nullptr)
259 {
260 csf.GetpModDoc()->SetModified(); // The tuning is changed so the modified flag is set.
261 }
262 #endif // MODPLUG_TRACKER
263
264 }
265 csf.Instruments[i]->pTuning = csf.GetDefaultTuning();
266
267 } else
268 {
269 //This 'else' happens probably only in case of corrupted file.
270 if(csf.Instruments[i])
271 csf.Instruments[i]->pTuning = csf.GetDefaultTuning();
272 }
273
274 }
275 //End read&set instrument tunings
276 }
277
278
ReadTuningMap(std::istream & iStrm,CSoundFile & csf,const size_t dummy,mpt::Charset charset)279 static void ReadTuningMap(std::istream& iStrm, CSoundFile& csf, const size_t dummy, mpt::Charset charset)
280 {
281 ReadTuningMapImpl(iStrm, csf, charset, dummy, false);
282 }
283
284
285 //////////////////////////////////////////////////////////
286 // Impulse Tracker IT file support
287
288
ITInstrToMPT(FileReader & file,ModInstrument & ins,uint16 trkvers)289 size_t CSoundFile::ITInstrToMPT(FileReader &file, ModInstrument &ins, uint16 trkvers)
290 {
291 if(trkvers < 0x0200)
292 {
293 // Load old format (IT 1.xx) instrument (early IT 2.xx modules may have cmwt set to 1.00 for backwards compatibility)
294 ITOldInstrument instrumentHeader;
295 if(!file.ReadStruct(instrumentHeader))
296 {
297 return 0;
298 } else
299 {
300 instrumentHeader.ConvertToMPT(ins);
301 return sizeof(ITOldInstrument);
302 }
303 } else
304 {
305 const FileReader::off_t offset = file.GetPosition();
306
307 // Try loading extended instrument... instSize will differ between normal and extended instruments.
308 ITInstrumentEx instrumentHeader;
309 file.ReadStructPartial(instrumentHeader);
310 size_t instSize = instrumentHeader.ConvertToMPT(ins, GetType());
311 file.Seek(offset + instSize);
312
313 // Try reading modular instrument data.
314 // Yes, it is completely idiotic that we have both this and LoadExtendedInstrumentProperties.
315 // This is only required for files saved with *really* old OpenMPT versions (pre-1.17-RC1).
316 // This chunk was also written in later versions (probably to maintain compatibility with
317 // those ancient versions), but this also means that redundant information is stored in the file.
318 // Starting from OpenMPT 1.25.02.07, this chunk is no longer written.
319 if(file.ReadMagic("MSNI"))
320 {
321 //...the next piece of data must be the total size of the modular data
322 FileReader modularData = file.ReadChunk(file.ReadUint32LE());
323 instSize += 8 + modularData.GetLength();
324 if(modularData.ReadMagic("GULP"))
325 {
326 ins.nMixPlug = modularData.ReadUint8();
327 if(ins.nMixPlug > MAX_MIXPLUGINS) ins.nMixPlug = 0;
328 }
329 }
330
331 return instSize;
332 }
333 }
334
335
CopyPatternName(CPattern & pattern,FileReader & file)336 static void CopyPatternName(CPattern &pattern, FileReader &file)
337 {
338 char name[MAX_PATTERNNAME] = "";
339 file.ReadString<mpt::String::maybeNullTerminated>(name, MAX_PATTERNNAME);
340 pattern.SetName(name);
341 }
342
343
344 // Get version of Schism Tracker that was used to create an IT/S3M file.
GetSchismTrackerVersion(uint16 cwtv,uint32 reserved)345 mpt::ustring CSoundFile::GetSchismTrackerVersion(uint16 cwtv, uint32 reserved)
346 {
347 // Schism Tracker version information in a nutshell:
348 // < 0x020: a proper version (files saved by such versions are likely very rare)
349 // = 0x020: any version between the 0.2a release (2005-04-29?) and 2007-04-17
350 // = 0x050: anywhere from 2007-04-17 to 2009-10-31
351 // > 0x050: the number of days since 2009-10-31
352 // = 0xFFF: any version starting from 2020-10-28 (exact version stored in reserved value)
353
354 cwtv &= 0xFFF;
355 if(cwtv > 0x050)
356 {
357 int32 date = SchismTrackerEpoch + (cwtv < 0xFFF ? cwtv - 0x050 : reserved);
358 int32 y = static_cast<int32>((Util::mul32to64(10000, date) + 14780) / 3652425);
359 int32 ddd = date - (365 * y + y / 4 - y / 100 + y / 400);
360 if(ddd < 0)
361 {
362 y--;
363 ddd = date - (365 * y + y / 4 - y / 100 + y / 400);
364 }
365 int32 mi = (100 * ddd + 52) / 3060;
366 return MPT_UFORMAT("Schism Tracker {}-{}-{}")(
367 mpt::ufmt::dec0<4>(y + (mi + 2) / 12),
368 mpt::ufmt::dec0<2>((mi + 2) % 12 + 1),
369 mpt::ufmt::dec0<2>(ddd - (mi * 306 + 5) / 10 + 1));
370 } else
371 {
372 return MPT_UFORMAT("Schism Tracker 0.{}")(mpt::ufmt::hex0<2>(cwtv));
373 }
374 }
375
376
ValidateHeader(const ITFileHeader & fileHeader)377 static bool ValidateHeader(const ITFileHeader &fileHeader)
378 {
379 if((std::memcmp(fileHeader.id, "IMPM", 4) && std::memcmp(fileHeader.id, "tpm.", 4))
380 || fileHeader.insnum > 0xFF
381 || fileHeader.smpnum >= MAX_SAMPLES
382 )
383 {
384 return false;
385 }
386 return true;
387 }
388
389
GetHeaderMinimumAdditionalSize(const ITFileHeader & fileHeader)390 static uint64 GetHeaderMinimumAdditionalSize(const ITFileHeader &fileHeader)
391 {
392 return fileHeader.ordnum + (fileHeader.insnum + fileHeader.smpnum + fileHeader.patnum) * 4;
393 }
394
395
ProbeFileHeaderIT(MemoryFileReader file,const uint64 * pfilesize)396 CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderIT(MemoryFileReader file, const uint64 *pfilesize)
397 {
398 ITFileHeader fileHeader;
399 if(!file.ReadStruct(fileHeader))
400 {
401 return ProbeWantMoreData;
402 }
403 if(!ValidateHeader(fileHeader))
404 {
405 return ProbeFailure;
406 }
407 return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
408 }
409
410
ReadIT(FileReader & file,ModLoadingFlags loadFlags)411 bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags)
412 {
413 file.Rewind();
414
415 ITFileHeader fileHeader;
416 if(!file.ReadStruct(fileHeader))
417 {
418 return false;
419 }
420 if(!ValidateHeader(fileHeader))
421 {
422 return false;
423 }
424 if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
425 {
426 return false;
427 }
428 if(loadFlags == onlyVerifyHeader)
429 {
430 return true;
431 }
432
433 InitializeGlobals(MOD_TYPE_IT);
434
435 bool interpretModPlugMade = false;
436 mpt::ustring madeWithTracker;
437
438 // OpenMPT crap at the end of file
439 size_t mptStartPos = 0;
440
441 if(!memcmp(fileHeader.id, "tpm.", 4))
442 {
443 // Legacy MPTM files (old 1.17.02.4x releases)
444 SetType(MOD_TYPE_MPT);
445 file.Seek(file.GetLength() - 4);
446 mptStartPos = file.ReadUint32LE();
447 } else
448 {
449 if(fileHeader.cwtv > 0x888 && fileHeader.cwtv <= 0xFFF)
450 {
451 file.Seek(file.GetLength() - 4);
452 mptStartPos = file.ReadUint32LE();
453 if(mptStartPos >= 0x100 && mptStartPos < file.GetLength())
454 {
455 if(file.Seek(mptStartPos) && file.ReadMagic("228"))
456 {
457 SetType(MOD_TYPE_MPT);
458 if(fileHeader.cwtv >= verMptFileVerLoadLimit)
459 {
460 AddToLog(LogError, U_("The file informed that it is incompatible with this version of OpenMPT. Loading was terminated."));
461 return false;
462 } else if(fileHeader.cwtv > verMptFileVer)
463 {
464 AddToLog(LogInformation, U_("The loaded file was made with a more recent OpenMPT version and this version may not be able to load all the features or play the file correctly."));
465 }
466 }
467 }
468 }
469
470 if(GetType() == MOD_TYPE_IT)
471 {
472 // Which tracker was used to make this?
473 if((fileHeader.cwtv & 0xF000) == 0x5000)
474 {
475 // OpenMPT Version number (Major.Minor)
476 // This will only be interpreted as "made with ModPlug" (i.e. disable compatible playback etc) if the "reserved" field is set to "OMPT" - else, compatibility was used.
477 uint32 mptVersion = (fileHeader.cwtv & 0x0FFF) << 16;
478 if(!memcmp(&fileHeader.reserved, "OMPT", 4))
479 interpretModPlugMade = true;
480 else if(mptVersion >= 0x01'29'00'00)
481 mptVersion |= fileHeader.reserved & 0xFFFF;
482 m_dwLastSavedWithVersion = Version(mptVersion);
483 } else if(fileHeader.cmwt == 0x888 || fileHeader.cwtv == 0x888)
484 {
485 // OpenMPT 1.17.02.26 (r122) to 1.18 (raped IT format)
486 // Exact version number will be determined later.
487 interpretModPlugMade = true;
488 m_dwLastSavedWithVersion = MPT_V("1.17.00.00");
489 } else if(fileHeader.cwtv == 0x0217 && fileHeader.cmwt == 0x0200 && fileHeader.reserved == 0)
490 {
491 if(memchr(fileHeader.chnpan, 0xFF, sizeof(fileHeader.chnpan)) != nullptr)
492 {
493 // ModPlug Tracker 1.16 (semi-raped IT format) or BeRoTracker (will be determined later)
494 m_dwLastSavedWithVersion = MPT_V("1.16.00.00");
495 madeWithTracker = U_("ModPlug Tracker 1.09 - 1.16");
496 } else
497 {
498 // OpenMPT 1.17 disguised as this in compatible mode,
499 // but never writes 0xFF in the pan map for unused channels (which is an invalid value).
500 m_dwLastSavedWithVersion = MPT_V("1.17.00.00");
501 madeWithTracker = U_("OpenMPT 1.17 (compatibility export)");
502 }
503 interpretModPlugMade = true;
504 } else if(fileHeader.cwtv == 0x0214 && fileHeader.cmwt == 0x0202 && fileHeader.reserved == 0)
505 {
506 // ModPlug Tracker b3.3 - 1.09, instruments 557 bytes apart
507 m_dwLastSavedWithVersion = MPT_V("1.09.00.00");
508 madeWithTracker = U_("ModPlug Tracker b3.3 - 1.09");
509 interpretModPlugMade = true;
510 } else if(fileHeader.cwtv == 0x0300 && fileHeader.cmwt == 0x0300 && fileHeader.reserved == 0 && fileHeader.ordnum == 256 && fileHeader.sep == 128 && fileHeader.pwd == 0)
511 {
512 // A rare variant used from OpenMPT 1.17.02.20 (r113) to 1.17.02.25 (r121), found e.g. in xTr1m-SD.it
513 m_dwLastSavedWithVersion = MPT_V("1.17.02.20");
514 interpretModPlugMade = true;
515 }
516 }
517 }
518
519 m_SongFlags.set(SONG_LINEARSLIDES, (fileHeader.flags & ITFileHeader::linearSlides) != 0);
520 m_SongFlags.set(SONG_ITOLDEFFECTS, (fileHeader.flags & ITFileHeader::itOldEffects) != 0);
521 m_SongFlags.set(SONG_ITCOMPATGXX, (fileHeader.flags & ITFileHeader::itCompatGxx) != 0);
522 m_SongFlags.set(SONG_EXFILTERRANGE, (fileHeader.flags & ITFileHeader::extendedFilterRange) != 0);
523
524 m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.songname);
525
526 // Read row highlights
527 if((fileHeader.special & ITFileHeader::embedPatternHighlights))
528 {
529 // MPT 1.09 and older (and maybe also newer) versions leave this blank (0/0), but have the "special" flag set.
530 // Newer versions of MPT and OpenMPT 1.17 *always* write 4/16 here.
531 // Thus, we will just ignore those old versions.
532 // Note: OpenMPT 1.17.03.02 was the first version to properly make use of the time signature in the IT header.
533 // This poses a small unsolvable problem:
534 // - In compatible mode, we cannot distinguish this version from earlier 1.17 releases.
535 // Thus we cannot know when to read this field or not (m_dwLastSavedWithVersion will always be 1.17.00.00).
536 // Luckily OpenMPT 1.17.03.02 should not be very wide-spread.
537 // - In normal mode the time signature is always present in the song extensions anyway. So it's okay if we read
538 // the signature here and maybe overwrite it later when parsing the song extensions.
539 if(!m_dwLastSavedWithVersion || m_dwLastSavedWithVersion >= MPT_V("1.17.03.02"))
540 {
541 m_nDefaultRowsPerBeat = fileHeader.highlight_minor;
542 m_nDefaultRowsPerMeasure = fileHeader.highlight_major;
543 }
544 }
545
546 // Global Volume
547 m_nDefaultGlobalVolume = fileHeader.globalvol << 1;
548 if(m_nDefaultGlobalVolume > MAX_GLOBAL_VOLUME)
549 m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME;
550 if(fileHeader.speed)
551 m_nDefaultSpeed = fileHeader.speed;
552 m_nDefaultTempo.Set(std::max(uint8(31), static_cast<uint8>(fileHeader.tempo)));
553 m_nSamplePreAmp = std::min(static_cast<uint8>(fileHeader.mv), uint8(128));
554
555 // Reading Channels Pan Positions
556 for(CHANNELINDEX i = 0; i < 64; i++) if(fileHeader.chnpan[i] != 0xFF)
557 {
558 ChnSettings[i].Reset();
559 ChnSettings[i].nVolume = Clamp<uint8, uint8>(fileHeader.chnvol[i], 0, 64);
560 if(fileHeader.chnpan[i] & 0x80) ChnSettings[i].dwFlags.set(CHN_MUTE);
561 uint8 n = fileHeader.chnpan[i] & 0x7F;
562 if(n <= 64) ChnSettings[i].nPan = n * 4;
563 if(n == 100) ChnSettings[i].dwFlags.set(CHN_SURROUND);
564 }
565
566 // Reading orders
567 file.Seek(sizeof(ITFileHeader));
568 if(GetType() == MOD_TYPE_MPT && fileHeader.cwtv > 0x88A && fileHeader.cwtv <= 0x88D)
569 {
570 // Deprecated format used for MPTm files created with OpenMPT 1.17.02.46 - 1.17.02.48.
571 uint16 version = file.ReadUint16LE();
572 if(version != 0)
573 return false;
574 uint32 numOrd = file.ReadUint32LE();
575 if(numOrd > ModSpecs::mptm.ordersMax || !ReadOrderFromFile<uint32le>(Order(), file, numOrd))
576 return false;
577 } else
578 {
579 ReadOrderFromFile<uint8>(Order(), file, fileHeader.ordnum, 0xFF, 0xFE);
580 }
581
582 // Reading instrument, sample and pattern offsets
583 std::vector<uint32le> insPos, smpPos, patPos;
584 if(!file.ReadVector(insPos, fileHeader.insnum)
585 || !file.ReadVector(smpPos, fileHeader.smpnum)
586 || !file.ReadVector(patPos, fileHeader.patnum))
587 {
588 return false;
589 }
590
591 // Find the first parapointer.
592 // This is used for finding out whether the edit history is actually stored in the file or not,
593 // as some early versions of Schism Tracker set the history flag, but didn't save anything.
594 // We will consider the history invalid if it ends after the first parapointer.
595 uint32 minPtr = std::numeric_limits<decltype(minPtr)>::max();
596 for(uint32 pos : insPos)
597 {
598 if(pos > 0 && pos < minPtr)
599 minPtr = pos;
600 }
601 for(uint32 pos : smpPos)
602 {
603 if(pos > 0 && pos < minPtr)
604 minPtr = pos;
605 }
606 for(uint32 pos : patPos)
607 {
608 if(pos > 0 && pos < minPtr)
609 minPtr = pos;
610 }
611 if(fileHeader.special & ITFileHeader::embedSongMessage)
612 {
613 minPtr = std::min(minPtr, fileHeader.msgoffset.get());
614 }
615
616 const bool possiblyUNMO3 = fileHeader.cmwt == 0x0214 && (fileHeader.cwtv == 0x0214 || fileHeader.cwtv == 0)
617 && fileHeader.highlight_major == 0 && fileHeader.highlight_minor == 0
618 && fileHeader.pwd == 0 && fileHeader.reserved == 0
619 && (fileHeader.flags & (ITFileHeader::useMIDIPitchController | ITFileHeader::reqEmbeddedMIDIConfig)) == 0;
620
621 if(possiblyUNMO3 && fileHeader.insnum == 0 && fileHeader.smpnum > 0 && file.GetPosition() + 4 * smpPos.size() + 2 <= minPtr)
622 {
623 // UNMO3 < v2.4.0.1 reserves some space for instrument parapointers even in sample mode.
624 // This makes reading MIDI macros and plugin information impossible.
625 // Note: While UNMO3 and CheeseTracker header fingerprints are almost identical, we cannot mis-detect CheeseTracker here,
626 // as it always sets the instrument mode flag and writes non-zero row highlights.
627 bool oldUNMO3 = true;
628 for(uint16 i = 0; i < fileHeader.smpnum; i++)
629 {
630 if(file.ReadUint32LE() != 0)
631 {
632 oldUNMO3 = false;
633 file.SkipBack(4 + i * 4);
634 break;
635 }
636 }
637 if(oldUNMO3)
638 {
639 madeWithTracker = U_("UNMO3 <= 2.4");
640 }
641 }
642
643 if(possiblyUNMO3 && fileHeader.cwtv == 0)
644 {
645 madeWithTracker = U_("UNMO3 v0/1");
646 }
647
648 // Reading IT Edit History Info
649 // This is only supposed to be present if bit 1 of the special flags is set.
650 // However, old versions of Schism and probably other trackers always set this bit
651 // even if they don't write the edit history count. So we have to filter this out...
652 // This is done by looking at the parapointers. If the history data ends after
653 // the first parapointer, we assume that it's actually no history data.
654 if(fileHeader.special & ITFileHeader::embedEditHistory)
655 {
656 const uint16 nflt = file.ReadUint16LE();
657
658 if(file.CanRead(nflt * sizeof(ITHistoryStruct)) && file.GetPosition() + nflt * sizeof(ITHistoryStruct) <= minPtr)
659 {
660 m_FileHistory.resize(nflt);
661 for(auto &mptHistory : m_FileHistory)
662 {
663 ITHistoryStruct itHistory;
664 file.ReadStruct(itHistory);
665 itHistory.ConvertToMPT(mptHistory);
666 }
667
668 if(possiblyUNMO3 && nflt == 0)
669 {
670 if(fileHeader.special & ITFileHeader::embedPatternHighlights)
671 madeWithTracker = U_("UNMO3 <= 2.4.0.1"); // Set together with MIDI macro embed flag
672 else
673 madeWithTracker = U_("UNMO3"); // Either 2.4.0.2+ or no MIDI macros embedded
674 }
675 } else
676 {
677 // Oops, we were not supposed to read this.
678 file.SkipBack(2);
679 }
680 } else if(possiblyUNMO3 && fileHeader.special <= 1)
681 {
682 // UNMO3 < v2.4.0.1 will set the edit history special bit iff the MIDI macro embed bit is also set,
683 // but it always writes the two extra bytes for the edit history length (zeroes).
684 // If MIDI macros are embedded, we are fine and end up in the first case of the if statement (read edit history).
685 // Otherwise we end up here and might have to read the edit history length.
686 if(file.ReadUint16LE() == 0)
687 {
688 madeWithTracker = U_("UNMO3 <= 2.4");
689 } else
690 {
691 // These were not zero bytes, but potentially belong to the upcoming MIDI config - need to skip back.
692 // I think the only application that could end up here is CheeseTracker, if it allows to write 0 for both row highlight values.
693 // IT 2.14 itself will always write the edit history.
694 file.SkipBack(2);
695 }
696 }
697
698 // Reading MIDI Output & Macros
699 bool hasMidiConfig = (fileHeader.flags & ITFileHeader::reqEmbeddedMIDIConfig) || (fileHeader.special & ITFileHeader::embedMIDIConfiguration);
700 if(hasMidiConfig && file.ReadStruct<MIDIMacroConfigData>(m_MidiCfg))
701 {
702 m_MidiCfg.Sanitize();
703 }
704
705 // Ignore MIDI data. Fixes some files like denonde.it that were made with old versions of Impulse Tracker (which didn't support Zxx filters) and have Zxx effects in the patterns.
706 if(fileHeader.cwtv < 0x0214)
707 {
708 m_MidiCfg.ClearZxxMacros();
709 }
710
711 // Read pattern names: "PNAM"
712 FileReader patNames;
713 if(file.ReadMagic("PNAM"))
714 {
715 patNames = file.ReadChunk(file.ReadUint32LE());
716 }
717
718 m_nChannels = 1;
719 // Read channel names: "CNAM"
720 if(file.ReadMagic("CNAM"))
721 {
722 FileReader chnNames = file.ReadChunk(file.ReadUint32LE());
723 const CHANNELINDEX readChns = std::min(MAX_BASECHANNELS, static_cast<CHANNELINDEX>(chnNames.GetLength() / MAX_CHANNELNAME));
724 m_nChannels = readChns;
725
726 for(CHANNELINDEX i = 0; i < readChns; i++)
727 {
728 chnNames.ReadString<mpt::String::maybeNullTerminated>(ChnSettings[i].szName, MAX_CHANNELNAME);
729 }
730 }
731
732 // Read mix plugins information
733 FileReader pluginChunk = file.ReadChunk((minPtr >= file.GetPosition()) ? minPtr - file.GetPosition() : file.BytesLeft());
734 const bool isBeRoTracker = LoadMixPlugins(pluginChunk);
735
736 // Read Song Message
737 if((fileHeader.special & ITFileHeader::embedSongMessage) && fileHeader.msglength > 0 && file.Seek(fileHeader.msgoffset))
738 {
739 // Generally, IT files should use CR for line endings. However, ChibiTracker uses LF. One could do...
740 // if(itHeader.cwtv == 0x0214 && itHeader.cmwt == 0x0214 && itHeader.reserved == ITFileHeader::chibiMagic) --> Chibi detected.
741 // But we'll just use autodetection here:
742 m_songMessage.Read(file, fileHeader.msglength, SongMessage::leAutodetect);
743 }
744
745 // Reading Instruments
746 m_nInstruments = 0;
747 if(fileHeader.flags & ITFileHeader::instrumentMode)
748 {
749 m_nInstruments = std::min(static_cast<INSTRUMENTINDEX>(fileHeader.insnum), static_cast<INSTRUMENTINDEX>(MAX_INSTRUMENTS - 1));
750 }
751 for(INSTRUMENTINDEX i = 0; i < GetNumInstruments(); i++)
752 {
753 if(insPos[i] > 0 && file.Seek(insPos[i]) && file.CanRead(fileHeader.cmwt < 0x200 ? sizeof(ITOldInstrument) : sizeof(ITInstrument)))
754 {
755 ModInstrument *instrument = AllocateInstrument(i + 1);
756 if(instrument != nullptr)
757 {
758 ITInstrToMPT(file, *instrument, fileHeader.cmwt);
759 // MIDI Pitch Wheel Depth is a global setting in IT. Apply it to all instruments.
760 instrument->midiPWD = fileHeader.pwd;
761 }
762 }
763 }
764
765 // In order to properly compute the position, in file, of eventual extended settings
766 // such as "attack" we need to keep the "real" size of the last sample as those extra
767 // setting will follow this sample in the file
768 FileReader::off_t lastSampleOffset = 0;
769 if(fileHeader.smpnum > 0)
770 {
771 lastSampleOffset = smpPos[fileHeader.smpnum - 1] + sizeof(ITSample);
772 }
773
774 bool possibleXMconversion = false;
775
776 // Reading Samples
777 m_nSamples = std::min(static_cast<SAMPLEINDEX>(fileHeader.smpnum), static_cast<SAMPLEINDEX>(MAX_SAMPLES - 1));
778 bool lastSampleCompressed = false;
779 for(SAMPLEINDEX i = 0; i < GetNumSamples(); i++)
780 {
781 ITSample sampleHeader;
782 if(smpPos[i] > 0 && file.Seek(smpPos[i]) && file.ReadStruct(sampleHeader))
783 {
784 // IT does not check for the IMPS magic, and some bad XM->IT converter out there doesn't write the magic bytes for empty sample slots.
785 ModSample &sample = Samples[i + 1];
786 size_t sampleOffset = sampleHeader.ConvertToMPT(sample);
787
788 m_szNames[i + 1] = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name);
789
790 if(!file.Seek(sampleOffset))
791 continue;
792
793 lastSampleCompressed = false;
794 if(sample.uFlags[CHN_ADLIB])
795 {
796 // FM instrument in MPTM
797 OPLPatch patch;
798 if(file.ReadArray(patch))
799 {
800 sample.SetAdlib(true, patch);
801 }
802 } else if(!sample.uFlags[SMP_KEEPONDISK])
803 {
804 SampleIO sampleIO = sampleHeader.GetSampleFormat(fileHeader.cwtv);
805 if(loadFlags & loadSampleData)
806 {
807 sampleIO.ReadSample(sample, file);
808 } else
809 {
810 if(sampleIO.IsVariableLengthEncoded())
811 lastSampleCompressed = true;
812 else
813 file.Skip(sampleIO.CalculateEncodedSize(sample.nLength));
814 }
815 if(sampleIO.GetEncoding() == SampleIO::unsignedPCM && sample.nLength != 0)
816 {
817 // There is some XM to IT converter (don't know which one) and it identifies as IT 2.04.
818 // The only safe way to distinguish it from an IT-saved file are the unsigned samples.
819 possibleXMconversion = true;
820 }
821 } else
822 {
823 // External sample in MPTM file
824 size_t strLen;
825 file.ReadVarInt(strLen);
826 if((loadFlags & loadSampleData) && strLen)
827 {
828 std::string filenameU8;
829 file.ReadString<mpt::String::maybeNullTerminated>(filenameU8, strLen);
830 #if defined(MPT_EXTERNAL_SAMPLES)
831 SetSamplePath(i + 1, mpt::PathString::FromUTF8(filenameU8));
832 #elif !defined(LIBOPENMPT_BUILD_TEST)
833 AddToLog(LogWarning, MPT_UFORMAT("Loading external sample {} ('{}') failed: External samples are not supported.")(i + 1, mpt::ToUnicode(mpt::Charset::UTF8, filenameU8)));
834 #endif // MPT_EXTERNAL_SAMPLES
835 } else
836 {
837 file.Skip(strLen);
838 }
839 }
840 lastSampleOffset = std::max(lastSampleOffset, file.GetPosition());
841 }
842 }
843 m_nSamples = std::max(SAMPLEINDEX(1), GetNumSamples());
844
845 if(possibleXMconversion && fileHeader.cwtv == 0x0204 && fileHeader.cmwt == 0x0200 && fileHeader.special == 0 && fileHeader.reserved == 0
846 && (fileHeader.flags & ~ITFileHeader::linearSlides) == (ITFileHeader::useStereoPlayback | ITFileHeader::instrumentMode | ITFileHeader::itOldEffects)
847 && fileHeader.globalvol == 128 && fileHeader.mv == 48 && fileHeader.sep == 128 && fileHeader.pwd == 0 && fileHeader.msglength == 0)
848 {
849 for(uint8 pan : fileHeader.chnpan)
850 {
851 if(pan != 0x20 && pan != 0xA0)
852 possibleXMconversion = false;
853 }
854 for(uint8 vol : fileHeader.chnvol)
855 {
856 if(vol != 0x40)
857 possibleXMconversion = false;
858 }
859 for(size_t i = 20; i < std::size(fileHeader.songname); i++)
860 {
861 if(fileHeader.songname[i] != 0)
862 possibleXMconversion = false;
863 }
864 if(possibleXMconversion)
865 madeWithTracker = U_("XM Conversion");
866 }
867
868 m_nMinPeriod = 0;
869 m_nMaxPeriod = int32_max;
870
871 PATTERNINDEX numPats = std::min(static_cast<PATTERNINDEX>(patPos.size()), GetModSpecifications().patternsMax);
872
873 if(numPats != patPos.size())
874 {
875 // Hack: Notify user here if file contains more patterns than what can be read.
876 AddToLog(LogWarning, MPT_UFORMAT("The module contains {} patterns but only {} patterns can be loaded in this OpenMPT version.")(patPos.size(), numPats));
877 }
878
879 if(!(loadFlags & loadPatternData))
880 {
881 numPats = 0;
882 }
883
884 // Checking for number of used channels, which is not explicitely specified in the file.
885 for(PATTERNINDEX pat = 0; pat < numPats; pat++)
886 {
887 if(patPos[pat] == 0 || !file.Seek(patPos[pat]))
888 continue;
889
890 uint16 len = file.ReadUint16LE();
891 ROWINDEX numRows = file.ReadUint16LE();
892
893 if(numRows < 1
894 || numRows > MAX_PATTERN_ROWS
895 || !file.Skip(4))
896 continue;
897
898 FileReader patternData = file.ReadChunk(len);
899 ROWINDEX row = 0;
900 std::vector<uint8> chnMask(GetNumChannels());
901
902 while(row < numRows && patternData.CanRead(1))
903 {
904 uint8 b = patternData.ReadUint8();
905 if(!b)
906 {
907 row++;
908 continue;
909 }
910
911 CHANNELINDEX ch = (b & IT_bitmask_patternChanField_c); // 0x7f We have some data grab a byte keeping only 7 bits
912 if(ch)
913 {
914 ch = (ch - 1);// & IT_bitmask_patternChanMask_c; // 0x3f mask of the byte again, keeping only 6 bits
915 }
916
917 if(ch >= chnMask.size())
918 {
919 chnMask.resize(ch + 1, 0);
920 }
921
922 if(b & IT_bitmask_patternChanEnabled_c) // 0x80 check if the upper bit is enabled.
923 {
924 chnMask[ch] = patternData.ReadUint8(); // set the channel mask for this channel.
925 }
926 // Channel used
927 if(chnMask[ch] & 0x0F) // if this channel is used set m_nChannels
928 {
929 if(ch >= GetNumChannels() && ch < MAX_BASECHANNELS)
930 {
931 m_nChannels = ch + 1;
932 }
933 }
934 // Now we actually update the pattern-row entry the note,instrument etc.
935 // Note
936 if(chnMask[ch] & 1)
937 patternData.Skip(1);
938 // Instrument
939 if(chnMask[ch] & 2)
940 patternData.Skip(1);
941 // Volume
942 if(chnMask[ch] & 4)
943 patternData.Skip(1);
944 // Effect
945 if(chnMask[ch] & 8)
946 patternData.Skip(2);
947 }
948 lastSampleOffset = std::max(lastSampleOffset, file.GetPosition());
949 }
950
951 // Compute extra instruments settings position
952 if(lastSampleOffset > 0)
953 {
954 file.Seek(lastSampleOffset);
955 if(lastSampleCompressed)
956 {
957 // If the last sample was compressed, we do not know where it ends.
958 // Hence, in case we decided not to decode the sample data, we now
959 // have to emulate this until we reach EOF or some instrument / song properties.
960 while(file.CanRead(4))
961 {
962 if(file.ReadMagic("XTPM") || file.ReadMagic("STPM"))
963 {
964 uint32 id = file.ReadUint32LE();
965 file.SkipBack(8);
966 // Our chunk IDs should only contain ASCII characters
967 if(!(id & 0x80808080) && (id & 0x60606060))
968 {
969 break;
970 }
971 }
972 file.Skip(file.ReadUint16LE());
973 }
974 }
975 }
976
977 // Load instrument and song extensions.
978 interpretModPlugMade |= LoadExtendedInstrumentProperties(file);
979 if(interpretModPlugMade && !isBeRoTracker)
980 {
981 m_playBehaviour.reset();
982 m_nMixLevels = MixLevels::Original;
983 }
984 // Need to do this before reading the patterns because m_nChannels might be modified by LoadExtendedSongProperties. *sigh*
985 LoadExtendedSongProperties(file, false, &interpretModPlugMade);
986
987 // Reading Patterns
988 Patterns.ResizeArray(numPats);
989 for(PATTERNINDEX pat = 0; pat < numPats; pat++)
990 {
991 if(patPos[pat] == 0 || !file.Seek(patPos[pat]))
992 {
993 // Empty 64-row pattern
994 if(!Patterns.Insert(pat, 64))
995 {
996 AddToLog(LogWarning, MPT_UFORMAT("Allocating patterns failed starting from pattern {}")(pat));
997 break;
998 }
999 // Now (after the Insert() call), we can read the pattern name.
1000 CopyPatternName(Patterns[pat], patNames);
1001 continue;
1002 }
1003
1004 uint16 len = file.ReadUint16LE();
1005 ROWINDEX numRows = file.ReadUint16LE();
1006
1007 if(!file.Skip(4)
1008 || !Patterns.Insert(pat, numRows))
1009 continue;
1010
1011 FileReader patternData = file.ReadChunk(len);
1012
1013 // Now (after the Insert() call), we can read the pattern name.
1014 CopyPatternName(Patterns[pat], patNames);
1015
1016 std::vector<uint8> chnMask(GetNumChannels());
1017 std::vector<ModCommand> lastValue(GetNumChannels(), ModCommand::Empty());
1018
1019 auto patData = Patterns[pat].begin();
1020 ROWINDEX row = 0;
1021 while(row < numRows && patternData.CanRead(1))
1022 {
1023 uint8 b = patternData.ReadUint8();
1024 if(!b)
1025 {
1026 row++;
1027 patData += GetNumChannels();
1028 continue;
1029 }
1030
1031 CHANNELINDEX ch = b & IT_bitmask_patternChanField_c; // 0x7f
1032
1033 if(ch)
1034 {
1035 ch = (ch - 1); //& IT_bitmask_patternChanMask_c; // 0x3f
1036 }
1037
1038 if(ch >= chnMask.size())
1039 {
1040 chnMask.resize(ch + 1, 0);
1041 lastValue.resize(ch + 1, ModCommand::Empty());
1042 MPT_ASSERT(chnMask.size() <= GetNumChannels());
1043 }
1044
1045 if(b & IT_bitmask_patternChanEnabled_c) // 0x80
1046 {
1047 chnMask[ch] = patternData.ReadUint8();
1048 }
1049
1050 // Now we grab the data for this particular row/channel.
1051 ModCommand dummy = ModCommand::Empty();
1052 ModCommand &m = ch < m_nChannels ? patData[ch] : dummy;
1053
1054 if(chnMask[ch] & 0x10)
1055 {
1056 m.note = lastValue[ch].note;
1057 }
1058 if(chnMask[ch] & 0x20)
1059 {
1060 m.instr = lastValue[ch].instr;
1061 }
1062 if(chnMask[ch] & 0x40)
1063 {
1064 m.volcmd = lastValue[ch].volcmd;
1065 m.vol = lastValue[ch].vol;
1066 }
1067 if(chnMask[ch] & 0x80)
1068 {
1069 m.command = lastValue[ch].command;
1070 m.param = lastValue[ch].param;
1071 }
1072 if(chnMask[ch] & 1) // Note
1073 {
1074 uint8 note = patternData.ReadUint8();
1075 if(note < 0x80)
1076 note += NOTE_MIN;
1077 if(!(GetType() & MOD_TYPE_MPT))
1078 {
1079 if(note > NOTE_MAX && note < 0xFD) note = NOTE_FADE;
1080 else if(note == 0xFD) note = NOTE_NONE;
1081 }
1082 m.note = note;
1083 lastValue[ch].note = note;
1084 }
1085 if(chnMask[ch] & 2)
1086 {
1087 uint8 instr = patternData.ReadUint8();
1088 m.instr = instr;
1089 lastValue[ch].instr = instr;
1090 }
1091 if(chnMask[ch] & 4)
1092 {
1093 uint8 vol = patternData.ReadUint8();
1094 // 0-64: Set Volume
1095 if(vol <= 64) { m.volcmd = VOLCMD_VOLUME; m.vol = vol; } else
1096 // 128-192: Set Panning
1097 if(vol >= 128 && vol <= 192) { m.volcmd = VOLCMD_PANNING; m.vol = vol - 128; } else
1098 // 65-74: Fine Volume Up
1099 if(vol < 75) { m.volcmd = VOLCMD_FINEVOLUP; m.vol = vol - 65; } else
1100 // 75-84: Fine Volume Down
1101 if(vol < 85) { m.volcmd = VOLCMD_FINEVOLDOWN; m.vol = vol - 75; } else
1102 // 85-94: Volume Slide Up
1103 if(vol < 95) { m.volcmd = VOLCMD_VOLSLIDEUP; m.vol = vol - 85; } else
1104 // 95-104: Volume Slide Down
1105 if(vol < 105) { m.volcmd = VOLCMD_VOLSLIDEDOWN; m.vol = vol - 95; } else
1106 // 105-114: Pitch Slide Up
1107 if(vol < 115) { m.volcmd = VOLCMD_PORTADOWN; m.vol = vol - 105; } else
1108 // 115-124: Pitch Slide Down
1109 if(vol < 125) { m.volcmd = VOLCMD_PORTAUP; m.vol = vol - 115; } else
1110 // 193-202: Portamento To
1111 if(vol >= 193 && vol <= 202) { m.volcmd = VOLCMD_TONEPORTAMENTO; m.vol = vol - 193; } else
1112 // 203-212: Vibrato depth
1113 if(vol >= 203 && vol <= 212)
1114 {
1115 m.volcmd = VOLCMD_VIBRATODEPTH;
1116 m.vol = vol - 203;
1117 // Old versions of ModPlug saved this as vibrato speed instead, so let's fix that.
1118 if(m.vol && m_dwLastSavedWithVersion && m_dwLastSavedWithVersion <= MPT_V("1.17.02.54"))
1119 m.volcmd = VOLCMD_VIBRATOSPEED;
1120 } else
1121 // 213-222: Unused (was velocity)
1122 // 223-232: Offset
1123 if(vol >= 223 && vol <= 232) { m.volcmd = VOLCMD_OFFSET; m.vol = vol - 223; }
1124 lastValue[ch].volcmd = m.volcmd;
1125 lastValue[ch].vol = m.vol;
1126 }
1127 // Reading command/param
1128 if(chnMask[ch] & 8)
1129 {
1130 const auto [command, param] = patternData.ReadArray<uint8, 2>();
1131 m.command = command;
1132 m.param = param;
1133 S3MConvert(m, true);
1134 // In some IT-compatible trackers, it is possible to input a parameter without a command.
1135 // In this case, we still need to update the last value memory. OpenMPT didn't do this until v1.25.01.07.
1136 // Example: ckbounce.it
1137 lastValue[ch].command = m.command;
1138 lastValue[ch].param = m.param;
1139 }
1140 }
1141 }
1142
1143 if(!m_dwLastSavedWithVersion && fileHeader.cwtv == 0x0888)
1144 {
1145 // Up to OpenMPT 1.17.02.45 (r165), it was possible that the "last saved with" field was 0
1146 // when saving a file in OpenMPT for the first time.
1147 m_dwLastSavedWithVersion = MPT_V("1.17.00.00");
1148 }
1149
1150 if(m_dwLastSavedWithVersion && madeWithTracker.empty())
1151 {
1152 madeWithTracker = U_("OpenMPT ") + mpt::ufmt::val(m_dwLastSavedWithVersion);
1153 if(memcmp(&fileHeader.reserved, "OMPT", 4) && (fileHeader.cwtv & 0xF000) == 0x5000)
1154 {
1155 madeWithTracker += U_(" (compatibility export)");
1156 } else if(m_dwLastSavedWithVersion.IsTestVersion())
1157 {
1158 madeWithTracker += U_(" (test build)");
1159 }
1160 } else
1161 {
1162 const int32 schismDateVersion = SchismTrackerEpoch + ((fileHeader.cwtv == 0x1FFF) ? fileHeader.reserved : (fileHeader.cwtv - 0x1050));
1163 switch(fileHeader.cwtv >> 12)
1164 {
1165 case 0:
1166 if(isBeRoTracker)
1167 {
1168 // Old versions
1169 madeWithTracker = U_("BeRoTracker");
1170 } else if(fileHeader.cwtv == 0x0214 && fileHeader.cmwt == 0x0200 && fileHeader.flags == 9 && fileHeader.special == 0
1171 && fileHeader.highlight_major == 0 && fileHeader.highlight_minor == 0
1172 && fileHeader.insnum == 0 && fileHeader.patnum + 1 == fileHeader.ordnum
1173 && fileHeader.globalvol == 128 && fileHeader.mv == 100 && fileHeader.speed == 1 && fileHeader.sep == 128 && fileHeader.pwd == 0
1174 && fileHeader.msglength == 0 && fileHeader.msgoffset == 0 && fileHeader.reserved == 0)
1175 {
1176 madeWithTracker = U_("OpenSPC conversion");
1177 } else if(fileHeader.cwtv == 0x0214 && fileHeader.cmwt == 0x0200 && fileHeader.highlight_major == 0 && fileHeader.highlight_minor == 0 && fileHeader.reserved == 0)
1178 {
1179 // ModPlug Tracker 1.00a5, instruments 560 bytes apart
1180 m_dwLastSavedWithVersion = MPT_V("1.00.00.A5");
1181 madeWithTracker = U_("ModPlug Tracker 1.00a5");
1182 interpretModPlugMade = true;
1183 } else if(fileHeader.cwtv == 0x0214 && fileHeader.cmwt == 0x0214 && !memcmp(&fileHeader.reserved, "CHBI", 4))
1184 {
1185 madeWithTracker = U_("ChibiTracker");
1186 m_playBehaviour.reset(kITShortSampleRetrig);
1187 } else if(fileHeader.cwtv == 0x0214 && fileHeader.cmwt == 0x0214 && fileHeader.special <= 1 && fileHeader.pwd == 0 && fileHeader.reserved == 0
1188 && (fileHeader.flags & (ITFileHeader::vol0Optimisations | ITFileHeader::instrumentMode | ITFileHeader::useMIDIPitchController | ITFileHeader::reqEmbeddedMIDIConfig | ITFileHeader::extendedFilterRange)) == ITFileHeader::instrumentMode
1189 && m_nSamples > 0 && (Samples[1].filename == "XXXXXXXX.YYY"))
1190 {
1191 madeWithTracker = U_("CheeseTracker");
1192 } else if(fileHeader.cwtv == 0 && madeWithTracker.empty())
1193 {
1194 madeWithTracker = U_("Unknown");
1195 } else if(fileHeader.cmwt < 0x0300 && madeWithTracker.empty())
1196 {
1197 if(fileHeader.cmwt > 0x0214)
1198 {
1199 madeWithTracker = U_("Impulse Tracker 2.15");
1200 } else if(fileHeader.cwtv > 0x0214)
1201 {
1202 // Patched update of IT 2.14 (0x0215 - 0x0217 == p1 - p3)
1203 // p4 (as found on modland) adds the ITVSOUND driver, but doesn't seem to change
1204 // anything as far as file saving is concerned.
1205 madeWithTracker = MPT_UFORMAT("Impulse Tracker 2.14p{}")(fileHeader.cwtv - 0x0214);
1206 } else
1207 {
1208 madeWithTracker = MPT_UFORMAT("Impulse Tracker {}.{}")((fileHeader.cwtv & 0x0F00) >> 8, mpt::ufmt::hex0<2>((fileHeader.cwtv & 0xFF)));
1209 }
1210 if(m_FileHistory.empty() && fileHeader.reserved != 0)
1211 {
1212 // Starting from version 2.07, IT stores the total edit time of a module in the "reserved" field
1213 uint32 editTime = DecodeITEditTimer(fileHeader.cwtv, fileHeader.reserved);
1214
1215 FileHistory hist;
1216 hist.openTime = static_cast<uint32>(editTime * (HISTORY_TIMER_PRECISION / 18.2));
1217 m_FileHistory.push_back(hist);
1218 }
1219 }
1220 break;
1221 case 1:
1222 madeWithTracker = GetSchismTrackerVersion(fileHeader.cwtv, fileHeader.reserved);
1223 // Hertz in linear mode: Added 2015-01-29, https://github.com/schismtracker/schismtracker/commit/671b30311082a0e7df041fca25f989b5d2478f69
1224 if(schismDateVersion < SchismVersionFromDate<2015, 01, 29>::date && m_SongFlags[SONG_LINEARSLIDES])
1225 m_playBehaviour.reset(kPeriodsAreHertz);
1226 // Hertz in Amiga mode: Added 2021-05-02, https://github.com/schismtracker/schismtracker/commit/c656a6cbd5aaf81198a7580faf81cb7960cb6afa
1227 else if(schismDateVersion < SchismVersionFromDate<2021, 05, 02>::date && !m_SongFlags[SONG_LINEARSLIDES])
1228 m_playBehaviour.reset(kPeriodsAreHertz);
1229 // Qxx with short samples: Added 2016-05-13, https://github.com/schismtracker/schismtracker/commit/e7b1461fe751554309fd403713c2a1ef322105ca
1230 if(schismDateVersion < SchismVersionFromDate<2016, 05, 13>::date)
1231 m_playBehaviour.reset(kITShortSampleRetrig);
1232 // Instrument pan doesn't override channel pan: Added 2021-05-02, https://github.com/schismtracker/schismtracker/commit/a34ec86dc819915debc9e06f4727b77bf2dd29ee
1233 if(schismDateVersion < SchismVersionFromDate<2021, 05, 02>::date)
1234 m_playBehaviour.reset(kITDoNotOverrideChannelPan);
1235 // Notes set instrument panning, not instrument numbers: Added 2021-05-02, https://github.com/schismtracker/schismtracker/commit/648f5116f984815c69e11d018b32dfec53c6b97a
1236 if(schismDateVersion < SchismVersionFromDate<2021, 05, 02>::date)
1237 m_playBehaviour.reset(kITPanningReset);
1238 // Imprecise calculation of ping-pong loop wraparound: Added 2021-11-01, https://github.com/schismtracker/schismtracker/commit/22cbb9b676e9c2c9feb7a6a17deca7a17ac138cc
1239 if(schismDateVersion < SchismVersionFromDate<2021, 11, 01>::date)
1240 m_playBehaviour.set(kImprecisePingPongLoops);
1241 // Pitch/Pan Separation can be overridden by panning commands: Added 2021-11-01, https://github.com/schismtracker/schismtracker/commit/6e9f1207015cae0fe1b829fff7bb867e02ec6dea
1242 if(schismDateVersion < SchismVersionFromDate<2021, 11, 01>::date)
1243 m_playBehaviour.reset(kITPitchPanSeparation);
1244 break;
1245 case 4:
1246 madeWithTracker = MPT_UFORMAT("pyIT {}.{}")((fileHeader.cwtv & 0x0F00) >> 8, mpt::ufmt::hex0<2>(fileHeader.cwtv & 0xFF));
1247 break;
1248 case 6:
1249 madeWithTracker = U_("BeRoTracker");
1250 break;
1251 case 7:
1252 if(fileHeader.cwtv == 0x7FFF && fileHeader.cmwt == 0x0215)
1253 madeWithTracker = U_("munch.py");
1254 else
1255 madeWithTracker = MPT_UFORMAT("ITMCK {}.{}.{}")((fileHeader.cwtv >> 8) & 0x0F, (fileHeader.cwtv >> 4) & 0x0F, fileHeader.cwtv & 0x0F);
1256 break;
1257 case 0xD:
1258 madeWithTracker = U_("spc2it");
1259 break;
1260 }
1261 }
1262
1263 if(GetType() == MOD_TYPE_MPT)
1264 {
1265 // START - mpt specific:
1266 if(fileHeader.cwtv > 0x0889 && file.Seek(mptStartPos))
1267 {
1268 LoadMPTMProperties(file, fileHeader.cwtv);
1269 }
1270 }
1271
1272 m_modFormat.formatName = (GetType() == MOD_TYPE_MPT) ? U_("OpenMPT MPTM") : MPT_UFORMAT("Impulse Tracker {}.{}")(fileHeader.cmwt >> 8, mpt::ufmt::hex0<2>(fileHeader.cmwt & 0xFF));
1273 m_modFormat.type = (GetType() == MOD_TYPE_MPT) ? U_("mptm") : U_("it");
1274 m_modFormat.madeWithTracker = std::move(madeWithTracker);
1275 m_modFormat.charset = m_dwLastSavedWithVersion ? mpt::Charset::Windows1252 : mpt::Charset::CP437;
1276
1277 return true;
1278 }
1279
1280
LoadMPTMProperties(FileReader & file,uint16 cwtv)1281 void CSoundFile::LoadMPTMProperties(FileReader &file, uint16 cwtv)
1282 {
1283 std::istringstream iStrm(mpt::buffer_cast<std::string>(file.GetRawDataAsByteVector()));
1284
1285 if(cwtv >= 0x88D)
1286 {
1287 srlztn::SsbRead ssb(iStrm);
1288 ssb.BeginRead("mptm", Version::Current().GetRawVersion());
1289 int8 useUTF8Tuning = 0;
1290 ssb.ReadItem(useUTF8Tuning, "UTF8Tuning");
1291 mpt::Charset TuningCharset = useUTF8Tuning ? mpt::Charset::UTF8 : GetCharsetInternal();
1292 ssb.ReadItem(GetTuneSpecificTunings(), "0", [TuningCharset](std::istream &iStrm, CTuningCollection &tc, const std::size_t dummy){ return ReadTuningCollection(iStrm, tc, dummy, TuningCharset); });
1293 ssb.ReadItem(*this, "1", [TuningCharset](std::istream& iStrm, CSoundFile& csf, const std::size_t dummy){ return ReadTuningMap(iStrm, csf, dummy, TuningCharset); });
1294 ssb.ReadItem(Order, "2", &ReadModSequenceOld);
1295 ssb.ReadItem(Patterns, FileIdPatterns, &ReadModPatterns);
1296 mpt::Charset sequenceDefaultCharset = GetCharsetInternal();
1297 ssb.ReadItem(Order, FileIdSequences, [sequenceDefaultCharset](std::istream &iStrm, ModSequenceSet &seq, std::size_t nSize){ return ReadModSequences(iStrm, seq, nSize, sequenceDefaultCharset); });
1298
1299 if(ssb.GetStatus() & srlztn::SNT_FAILURE)
1300 {
1301 AddToLog(LogError, U_("Unknown error occurred while deserializing file."));
1302 }
1303 } else
1304 {
1305 // Loading for older files.
1306 mpt::ustring name;
1307 if(GetTuneSpecificTunings().Deserialize(iStrm, name, GetCharsetInternal()) != Tuning::SerializationResult::Success)
1308 {
1309 AddToLog(LogError, U_("Loading tune specific tunings failed."));
1310 } else
1311 {
1312 ReadTuningMapImpl(iStrm, *this, GetCharsetInternal(), 0, cwtv < 0x88C);
1313 }
1314 }
1315 }
1316
1317
1318 #ifndef MODPLUG_NO_FILESAVE
1319
1320 // Save edit history. Pass a null pointer for *f to retrieve the number of bytes that would be written.
SaveITEditHistory(const CSoundFile & sndFile,std::ostream * file)1321 static uint32 SaveITEditHistory(const CSoundFile &sndFile, std::ostream *file)
1322 {
1323 size_t num = sndFile.GetFileHistory().size();
1324 #ifdef MODPLUG_TRACKER
1325 const CModDoc *pModDoc = sndFile.GetpModDoc();
1326 num += (pModDoc != nullptr) ? 1 : 0; // + 1 for this session
1327 #endif // MODPLUG_TRACKER
1328
1329 uint16 fnum = mpt::saturate_cast<uint16>(num); // Number of entries that are actually going to be written
1330 const uint32 bytesWritten = 2 + fnum * 8; // Number of bytes that are actually going to be written
1331
1332 if(!file)
1333 {
1334 return bytesWritten;
1335 }
1336 std::ostream & f = *file;
1337
1338 // Write number of history entries
1339 mpt::IO::WriteIntLE(f, fnum);
1340
1341 // Write history data
1342 const size_t start = (num > uint16_max) ? num - uint16_max : 0;
1343 for(size_t n = start; n < num; n++)
1344 {
1345 FileHistory mptHistory;
1346
1347 #ifdef MODPLUG_TRACKER
1348 if(n < sndFile.GetFileHistory().size())
1349 #endif // MODPLUG_TRACKER
1350 {
1351 // Previous timestamps
1352 mptHistory = sndFile.GetFileHistory()[n];
1353 #ifdef MODPLUG_TRACKER
1354 } else if(pModDoc != nullptr)
1355 {
1356 // Current ("new") timestamp
1357 const time_t creationTime = pModDoc->GetCreationTime();
1358
1359 MemsetZero(mptHistory.loadDate);
1360 //localtime_s(&loadDate, &creationTime);
1361 const tm* const p = localtime(&creationTime);
1362 if (p != nullptr)
1363 mptHistory.loadDate = *p;
1364 else
1365 sndFile.AddToLog(LogError, U_("Unable to retrieve current time."));
1366
1367 mptHistory.openTime = (uint32)(difftime(time(nullptr), creationTime) * HISTORY_TIMER_PRECISION);
1368 #endif // MODPLUG_TRACKER
1369 }
1370
1371 ITHistoryStruct itHistory;
1372 itHistory.ConvertToIT(mptHistory);
1373 mpt::IO::Write(f, itHistory);
1374 }
1375
1376 return bytesWritten;
1377 }
1378
1379
SaveIT(std::ostream & f,const mpt::PathString & filename,bool compatibilityExport)1380 bool CSoundFile::SaveIT(std::ostream &f, const mpt::PathString &filename, bool compatibilityExport)
1381 {
1382
1383 const CModSpecifications &specs = (GetType() == MOD_TYPE_MPT ? ModSpecs::mptm : (compatibilityExport ? ModSpecs::it : ModSpecs::itEx));
1384
1385 uint32 dwChnNamLen;
1386 ITFileHeader itHeader;
1387 uint64 dwPos = 0;
1388 uint32 dwHdrPos = 0, dwExtra = 0;
1389
1390 // Writing Header
1391 MemsetZero(itHeader);
1392 dwChnNamLen = 0;
1393 memcpy(itHeader.id, "IMPM", 4);
1394 mpt::String::WriteBuf(mpt::String::nullTerminated, itHeader.songname) = m_songName;
1395
1396 itHeader.highlight_minor = (uint8)std::min(m_nDefaultRowsPerBeat, ROWINDEX(uint8_max));
1397 itHeader.highlight_major = (uint8)std::min(m_nDefaultRowsPerMeasure, ROWINDEX(uint8_max));
1398
1399 if(GetType() == MOD_TYPE_MPT)
1400 {
1401 itHeader.ordnum = Order().GetLengthTailTrimmed();
1402 if(Order().NeedsExtraDatafield() && itHeader.ordnum > 256)
1403 {
1404 // If there are more order items, write them elsewhere.
1405 itHeader.ordnum = 256;
1406 }
1407 } else
1408 {
1409 // An additional "---" pattern is appended so Impulse Tracker won't ignore the last order item.
1410 // Interestingly, this can exceed IT's 256 order limit. Also, IT will always save at least two orders.
1411 itHeader.ordnum = std::min(Order().GetLengthTailTrimmed(), specs.ordersMax) + 1;
1412 if(itHeader.ordnum < 2)
1413 itHeader.ordnum = 2;
1414 }
1415
1416 itHeader.insnum = std::min(m_nInstruments, specs.instrumentsMax);
1417 itHeader.smpnum = std::min(m_nSamples, specs.samplesMax);
1418 itHeader.patnum = std::min(Patterns.GetNumPatterns(), specs.patternsMax);
1419
1420 // Parapointers
1421 std::vector<uint32le> patpos(itHeader.patnum);
1422 std::vector<uint32le> smppos(itHeader.smpnum);
1423 std::vector<uint32le> inspos(itHeader.insnum);
1424
1425 //VERSION
1426 if(GetType() == MOD_TYPE_MPT)
1427 {
1428 // MPTM
1429 itHeader.cwtv = verMptFileVer; // Used in OMPT-hack versioning.
1430 itHeader.cmwt = 0x888;
1431 } else
1432 {
1433 // IT
1434 const uint32 mptVersion = Version::Current().GetRawVersion();
1435 itHeader.cwtv = 0x5000 | static_cast<uint16>((mptVersion >> 16) & 0x0FFF); // format: txyy (t = tracker ID, x = version major, yy = version minor), e.g. 0x5117 (OpenMPT = 5, 117 = v1.17)
1436 itHeader.cmwt = 0x0214; // Common compatible tracker :)
1437 // Hack from schism tracker:
1438 for(INSTRUMENTINDEX nIns = 1; nIns <= GetNumInstruments(); nIns++)
1439 {
1440 if(Instruments[nIns] && Instruments[nIns]->PitchEnv.dwFlags[ENV_FILTER])
1441 {
1442 itHeader.cmwt = 0x0216;
1443 break;
1444 }
1445 }
1446
1447 if(compatibilityExport)
1448 itHeader.reserved = mptVersion & 0xFFFF;
1449 else
1450 memcpy(&itHeader.reserved, "OMPT", 4);
1451 }
1452
1453 itHeader.flags = ITFileHeader::useStereoPlayback | ITFileHeader::useMIDIPitchController;
1454 itHeader.special = ITFileHeader::embedEditHistory | ITFileHeader::embedPatternHighlights;
1455 if(m_nInstruments) itHeader.flags |= ITFileHeader::instrumentMode;
1456 if(m_SongFlags[SONG_LINEARSLIDES]) itHeader.flags |= ITFileHeader::linearSlides;
1457 if(m_SongFlags[SONG_ITOLDEFFECTS]) itHeader.flags |= ITFileHeader::itOldEffects;
1458 if(m_SongFlags[SONG_ITCOMPATGXX]) itHeader.flags |= ITFileHeader::itCompatGxx;
1459 if(m_SongFlags[SONG_EXFILTERRANGE] && !compatibilityExport) itHeader.flags |= ITFileHeader::extendedFilterRange;
1460
1461 itHeader.globalvol = static_cast<uint8>(m_nDefaultGlobalVolume / 2u);
1462 itHeader.mv = static_cast<uint8>(std::min(m_nSamplePreAmp, uint32(128)));
1463 itHeader.speed = mpt::saturate_cast<uint8>(m_nDefaultSpeed);
1464 itHeader.tempo = mpt::saturate_cast<uint8>(m_nDefaultTempo.GetInt()); // We save the real tempo in an extension below if it exceeds 255.
1465 itHeader.sep = 128; // pan separation
1466 // IT doesn't have a per-instrument Pitch Wheel Depth setting, so we just store the first non-zero PWD setting in the header.
1467 for(INSTRUMENTINDEX ins = 1; ins <= GetNumInstruments(); ins++)
1468 {
1469 if(Instruments[ins] != nullptr && Instruments[ins]->midiPWD != 0)
1470 {
1471 itHeader.pwd = static_cast<uint8>(std::abs(Instruments[ins]->midiPWD));
1472 break;
1473 }
1474 }
1475
1476 dwHdrPos = sizeof(itHeader) + itHeader.ordnum;
1477 // Channel Pan and Volume
1478 memset(itHeader.chnpan, 0xA0, 64);
1479 memset(itHeader.chnvol, 64, 64);
1480
1481 for(CHANNELINDEX ich = 0; ich < std::min(m_nChannels, CHANNELINDEX(64)); ich++) // Header only has room for settings for 64 chans...
1482 {
1483 itHeader.chnpan[ich] = (uint8)(ChnSettings[ich].nPan >> 2);
1484 if (ChnSettings[ich].dwFlags[CHN_SURROUND]) itHeader.chnpan[ich] = 100;
1485 itHeader.chnvol[ich] = (uint8)(ChnSettings[ich].nVolume);
1486 #ifdef MODPLUG_TRACKER
1487 if(TrackerSettings::Instance().MiscSaveChannelMuteStatus)
1488 #endif
1489 if (ChnSettings[ich].dwFlags[CHN_MUTE]) itHeader.chnpan[ich] |= 0x80;
1490 }
1491
1492 // Channel names
1493 if(!compatibilityExport)
1494 {
1495 for(CHANNELINDEX i = 0; i < m_nChannels; i++)
1496 {
1497 if(ChnSettings[i].szName[0])
1498 {
1499 dwChnNamLen = (i + 1) * MAX_CHANNELNAME;
1500 }
1501 }
1502 if(dwChnNamLen) dwExtra += dwChnNamLen + 8;
1503 }
1504
1505 if(!m_MidiCfg.IsMacroDefaultSetupUsed())
1506 {
1507 itHeader.flags |= ITFileHeader::reqEmbeddedMIDIConfig;
1508 itHeader.special |= ITFileHeader::embedMIDIConfiguration;
1509 dwExtra += sizeof(MIDIMacroConfigData);
1510 }
1511
1512 // Pattern Names
1513 const PATTERNINDEX numNamedPats = compatibilityExport ? 0 : Patterns.GetNumNamedPatterns();
1514 if(numNamedPats > 0)
1515 {
1516 dwExtra += (numNamedPats * MAX_PATTERNNAME) + 8;
1517 }
1518
1519 // Mix Plugins. Just calculate the size of this extra block for now.
1520 if(!compatibilityExport)
1521 {
1522 dwExtra += SaveMixPlugins(nullptr, true);
1523 }
1524
1525 // Edit History. Just calculate the size of this extra block for now.
1526 dwExtra += SaveITEditHistory(*this, nullptr);
1527
1528 // Comments
1529 uint16 msglength = 0;
1530 if(!m_songMessage.empty())
1531 {
1532 itHeader.special |= ITFileHeader::embedSongMessage;
1533 itHeader.msglength = msglength = mpt::saturate_cast<uint16>(m_songMessage.length() + 1u);
1534 itHeader.msgoffset = dwHdrPos + dwExtra + (itHeader.insnum + itHeader.smpnum + itHeader.patnum) * 4;
1535 }
1536
1537 // Write file header
1538 mpt::IO::Write(f, itHeader);
1539
1540 Order().WriteAsByte(f, itHeader.ordnum);
1541 mpt::IO::Write(f, inspos);
1542 mpt::IO::Write(f, smppos);
1543 mpt::IO::Write(f, patpos);
1544
1545 // Writing edit history information
1546 SaveITEditHistory(*this, &f);
1547
1548 // Writing midi cfg
1549 if(itHeader.flags & ITFileHeader::reqEmbeddedMIDIConfig)
1550 {
1551 mpt::IO::Write(f, static_cast<MIDIMacroConfigData &>(m_MidiCfg));
1552 }
1553
1554 // Writing pattern names
1555 if(numNamedPats)
1556 {
1557 mpt::IO::WriteRaw(f, "PNAM", 4);
1558 mpt::IO::WriteIntLE<uint32>(f, numNamedPats * MAX_PATTERNNAME);
1559
1560 for(PATTERNINDEX pat = 0; pat < numNamedPats; pat++)
1561 {
1562 char name[MAX_PATTERNNAME];
1563 mpt::String::WriteBuf(mpt::String::maybeNullTerminated, name) = Patterns[pat].GetName();
1564 mpt::IO::Write(f, name);
1565 }
1566 }
1567
1568 // Writing channel names
1569 if(dwChnNamLen && !compatibilityExport)
1570 {
1571 mpt::IO::WriteRaw(f, "CNAM", 4);
1572 mpt::IO::WriteIntLE<uint32>(f, dwChnNamLen);
1573 uint32 nChnNames = dwChnNamLen / MAX_CHANNELNAME;
1574 for(uint32 inam = 0; inam < nChnNames; inam++)
1575 {
1576 char name[MAX_CHANNELNAME];
1577 mpt::String::WriteBuf(mpt::String::maybeNullTerminated, name) = ChnSettings[inam].szName;
1578 mpt::IO::Write(f, name);
1579 }
1580 }
1581
1582 // Writing mix plugins info
1583 if(!compatibilityExport)
1584 {
1585 SaveMixPlugins(&f, false);
1586 }
1587
1588 // Writing song message
1589 dwPos = dwHdrPos + dwExtra + (itHeader.insnum + itHeader.smpnum + itHeader.patnum) * 4;
1590 if(itHeader.special & ITFileHeader::embedSongMessage)
1591 {
1592 dwPos += msglength;
1593 mpt::IO::WriteRaw(f, m_songMessage.c_str(), msglength);
1594 }
1595
1596 // Writing instruments
1597 const ModInstrument dummyInstr;
1598 for(INSTRUMENTINDEX nins = 1; nins <= itHeader.insnum; nins++)
1599 {
1600 ITInstrumentEx iti;
1601 uint32 instSize;
1602
1603 const ModInstrument &instr = (Instruments[nins] != nullptr) ? *Instruments[nins] : dummyInstr;
1604 instSize = iti.ConvertToIT(instr, compatibilityExport, *this);
1605
1606 // Writing instrument
1607 inspos[nins - 1] = static_cast<uint32>(dwPos);
1608 dwPos += instSize;
1609 mpt::IO::WritePartial(f, iti, instSize);
1610 }
1611
1612 // Writing dummy sample headers (until we know the correct sample data offset)
1613 ITSample itss;
1614 MemsetZero(itss);
1615 for(SAMPLEINDEX smp = 0; smp < itHeader.smpnum; smp++)
1616 {
1617 smppos[smp] = static_cast<uint32>(dwPos);
1618 dwPos += sizeof(ITSample);
1619 mpt::IO::Write(f, itss);
1620 }
1621
1622 // Writing Patterns
1623 bool needsMptPatSave = false;
1624 for(PATTERNINDEX pat = 0; pat < itHeader.patnum; pat++)
1625 {
1626 uint32 dwPatPos = static_cast<uint32>(dwPos);
1627 if (!Patterns.IsValidPat(pat)) continue;
1628
1629 if(Patterns[pat].GetOverrideSignature())
1630 needsMptPatSave = true;
1631
1632 // Check for empty pattern
1633 if(Patterns[pat].GetNumRows() == 64 && Patterns.IsPatternEmpty(pat))
1634 {
1635 patpos[pat] = 0;
1636 continue;
1637 }
1638
1639 patpos[pat] = static_cast<uint32>(dwPos);
1640
1641 // Write pattern header
1642 ROWINDEX writeRows = mpt::saturate_cast<uint16>(Patterns[pat].GetNumRows());
1643 uint16 writeSize = 0;
1644 uint16le patinfo[4];
1645 patinfo[0] = 0;
1646 patinfo[1] = static_cast<uint16>(writeRows);
1647 patinfo[2] = 0;
1648 patinfo[3] = 0;
1649
1650 mpt::IO::Write(f, patinfo);
1651 dwPos += 8;
1652
1653 struct ChnState { ModCommand lastCmd; uint8 mask = 0xFF; };
1654 const CHANNELINDEX maxChannels = std::min(specs.channelsMax, GetNumChannels());
1655 std::vector<ChnState> chnStates(maxChannels);
1656 // Maximum 7 bytes per cell, plus end of row marker, so this buffer is always large enough to cover one row.
1657 std::vector<uint8> buf(7 * maxChannels + 1);
1658
1659 for(ROWINDEX row = 0; row < writeRows; row++)
1660 {
1661 uint32 len = 0;
1662 const ModCommand *m = Patterns[pat].GetpModCommand(row, 0);
1663
1664 for(CHANNELINDEX ch = 0; ch < maxChannels; ch++, m++)
1665 {
1666 // Skip mptm-specific notes.
1667 if(m->IsPcNote())
1668 {
1669 needsMptPatSave = true;
1670 continue;
1671 }
1672
1673 auto &chnState = chnStates[ch];
1674 uint8 b = 0;
1675 uint8 command = m->command;
1676 uint8 param = m->param;
1677 uint8 vol = 0xFF;
1678 uint8 note = m->note;
1679 if (note != NOTE_NONE) b |= 1;
1680 if (m->IsNote()) note -= NOTE_MIN;
1681 if (note == NOTE_FADE && GetType() != MOD_TYPE_MPT) note = 0xF6;
1682 if (m->instr) b |= 2;
1683 if (m->volcmd != VOLCMD_NONE)
1684 {
1685 vol = std::min(m->vol, uint8(9));
1686 switch(m->volcmd)
1687 {
1688 case VOLCMD_VOLUME: vol = std::min(m->vol, uint8(64)); break;
1689 case VOLCMD_PANNING: vol = std::min(m->vol, uint8(64)) + 128; break;
1690 case VOLCMD_VOLSLIDEUP: vol += 85; break;
1691 case VOLCMD_VOLSLIDEDOWN: vol += 95; break;
1692 case VOLCMD_FINEVOLUP: vol += 65; break;
1693 case VOLCMD_FINEVOLDOWN: vol += 75; break;
1694 case VOLCMD_VIBRATODEPTH: vol += 203; break;
1695 case VOLCMD_TONEPORTAMENTO: vol += 193; break;
1696 case VOLCMD_PORTADOWN: vol += 105; break;
1697 case VOLCMD_PORTAUP: vol += 115; break;
1698 case VOLCMD_VIBRATOSPEED:
1699 if(command == CMD_NONE)
1700 {
1701 // Move unsupported command if possible
1702 command = CMD_VIBRATO;
1703 param = std::min(m->vol, uint8(15)) << 4;
1704 vol = 0xFF;
1705 } else
1706 {
1707 vol = 203;
1708 }
1709 break;
1710 case VOLCMD_OFFSET:
1711 if(!compatibilityExport)
1712 vol += 223;
1713 else
1714 vol = 0xFF;
1715 break;
1716 default: vol = 0xFF;
1717 }
1718 }
1719 if (vol != 0xFF) b |= 4;
1720 if (command != CMD_NONE)
1721 {
1722 S3MSaveConvert(command, param, true, compatibilityExport);
1723 if (command) b |= 8;
1724 }
1725 // Packing information
1726 if (b)
1727 {
1728 // Same note ?
1729 if (b & 1)
1730 {
1731 if ((note == chnState.lastCmd.note) && (chnState.lastCmd.volcmd & 1))
1732 {
1733 b &= ~1;
1734 b |= 0x10;
1735 } else
1736 {
1737 chnState.lastCmd.note = note;
1738 chnState.lastCmd.volcmd |= 1;
1739 }
1740 }
1741 // Same instrument ?
1742 if (b & 2)
1743 {
1744 if ((m->instr == chnState.lastCmd.instr) && (chnState.lastCmd.volcmd & 2))
1745 {
1746 b &= ~2;
1747 b |= 0x20;
1748 } else
1749 {
1750 chnState.lastCmd.instr = m->instr;
1751 chnState.lastCmd.volcmd |= 2;
1752 }
1753 }
1754 // Same volume column byte ?
1755 if (b & 4)
1756 {
1757 if ((vol == chnState.lastCmd.vol) && (chnState.lastCmd.volcmd & 4))
1758 {
1759 b &= ~4;
1760 b |= 0x40;
1761 } else
1762 {
1763 chnState.lastCmd.vol = vol;
1764 chnState.lastCmd.volcmd |= 4;
1765 }
1766 }
1767 // Same command / param ?
1768 if (b & 8)
1769 {
1770 if ((command == chnState.lastCmd.command) && (param == chnState.lastCmd.param) && (chnState.lastCmd.volcmd & 8))
1771 {
1772 b &= ~8;
1773 b |= 0x80;
1774 } else
1775 {
1776 chnState.lastCmd.command = command;
1777 chnState.lastCmd.param = param;
1778 chnState.lastCmd.volcmd |= 8;
1779 }
1780 }
1781 if (b != chnState.mask)
1782 {
1783 chnState.mask = b;
1784 buf[len++] = static_cast<uint8>((ch + 1) | IT_bitmask_patternChanEnabled_c);
1785 buf[len++] = b;
1786 } else
1787 {
1788 buf[len++] = static_cast<uint8>(ch + 1);
1789 }
1790 if (b & 1) buf[len++] = note;
1791 if (b & 2) buf[len++] = m->instr;
1792 if (b & 4) buf[len++] = vol;
1793 if (b & 8)
1794 {
1795 buf[len++] = command;
1796 buf[len++] = param;
1797 }
1798 }
1799 }
1800 buf[len++] = 0;
1801 if(writeSize > uint16_max - len)
1802 {
1803 AddToLog(LogWarning, MPT_UFORMAT("Warning: File format limit was reached. Some pattern data may not get written to file. (pattern {})")(pat));
1804 break;
1805 } else
1806 {
1807 dwPos += len;
1808 writeSize += (uint16)len;
1809 mpt::IO::WriteRaw(f, buf.data(), len);
1810 }
1811 }
1812
1813 mpt::IO::SeekAbsolute(f, dwPatPos);
1814 patinfo[0] = writeSize;
1815 mpt::IO::Write(f, patinfo);
1816 mpt::IO::SeekAbsolute(f, dwPos);
1817 }
1818 // Writing Sample Data
1819 for(SAMPLEINDEX smp = 1; smp <= itHeader.smpnum; smp++)
1820 {
1821 const ModSample &sample = Samples[smp];
1822 #ifdef MODPLUG_TRACKER
1823 uint32 type = GetType() == MOD_TYPE_IT ? 1 : 4;
1824 if(compatibilityExport) type = 2;
1825 bool compress = ((((sample.GetNumChannels() > 1) ? TrackerSettings::Instance().MiscITCompressionStereo : TrackerSettings::Instance().MiscITCompressionMono) & type) != 0);
1826 #else
1827 bool compress = false;
1828 #endif // MODPLUG_TRACKER
1829 // Old MPT, DUMB and probably other libraries will only consider the IT2.15 compression flag if the header version also indicates IT2.15.
1830 // MilkyTracker <= 0.90.85 assumes IT2.15 compression with cmwt == 0x215, ignoring the delta flag completely.
1831 itss.ConvertToIT(sample, GetType(), compress, itHeader.cmwt >= 0x215, GetType() == MOD_TYPE_MPT);
1832 const bool isExternal = itss.cvt == ITSample::cvtExternalSample;
1833
1834 mpt::String::WriteBuf(mpt::String::nullTerminated, itss.name) = m_szNames[smp];
1835
1836 itss.samplepointer = static_cast<uint32>(dwPos);
1837 if(dwPos > uint32_max)
1838 {
1839 // Sample position does not fit into sample pointer!
1840 AddToLog(LogWarning, MPT_UFORMAT("Cannot save sample {}: File size exceeds 4 GB.")(smp));
1841 itss.samplepointer = 0;
1842 itss.length = 0;
1843 }
1844 SmpLength smpLength = itss.length; // Possibly truncated to 2^32 samples
1845 mpt::IO::SeekAbsolute(f, smppos[smp - 1]);
1846 mpt::IO::Write(f, itss);
1847 if(dwPos > uint32_max)
1848 {
1849 continue;
1850 }
1851 // TODO this actually wraps around at 2 GB, so we either need to use the 64-bit seek API or warn earlier!
1852 mpt::IO::SeekAbsolute(f, dwPos);
1853 if(!isExternal)
1854 {
1855 if(sample.nLength > smpLength && smpLength != 0)
1856 {
1857 // Sample length does not fit into IT header!
1858 AddToLog(LogWarning, MPT_UFORMAT("Truncating sample {}: Length exceeds exceeds 4 gigasamples.")(smp));
1859 }
1860 dwPos += itss.GetSampleFormat().WriteSample(f, sample, smpLength);
1861 } else
1862 {
1863 #ifdef MPT_EXTERNAL_SAMPLES
1864 const std::string filenameU8 = GetSamplePath(smp).AbsolutePathToRelative(filename.GetPath()).ToUTF8();
1865 const size_t strSize = filenameU8.size();
1866 size_t intBytes = 0;
1867 if(mpt::IO::WriteVarInt(f, strSize, &intBytes))
1868 {
1869 dwPos += intBytes + strSize;
1870 mpt::IO::WriteRaw(f, filenameU8.data(), strSize);
1871 }
1872 #else
1873 MPT_UNREFERENCED_PARAMETER(filename);
1874 #endif // MPT_EXTERNAL_SAMPLES
1875 }
1876 }
1877
1878 //Save hacked-on extra info
1879 if(!compatibilityExport)
1880 {
1881 if(GetNumInstruments())
1882 {
1883 SaveExtendedInstrumentProperties(itHeader.insnum, f);
1884 }
1885 SaveExtendedSongProperties(f);
1886 }
1887
1888 // Updating offsets
1889 mpt::IO::SeekAbsolute(f, dwHdrPos);
1890 mpt::IO::Write(f, inspos);
1891 mpt::IO::Write(f, smppos);
1892 mpt::IO::Write(f, patpos);
1893
1894 if(GetType() == MOD_TYPE_IT)
1895 {
1896 return true;
1897 }
1898
1899 //hack
1900 //BEGIN: MPT SPECIFIC:
1901
1902 bool success = true;
1903
1904 mpt::IO::SeekEnd(f);
1905
1906 const mpt::IO::Offset MPTStartPos = mpt::IO::TellWrite(f);
1907
1908 srlztn::SsbWrite ssb(f);
1909 ssb.BeginWrite("mptm", Version::Current().GetRawVersion());
1910
1911 if(GetTuneSpecificTunings().GetNumTunings() > 0 || AreNonDefaultTuningsUsed(*this))
1912 ssb.WriteItem(int8(1), "UTF8Tuning");
1913 if(GetTuneSpecificTunings().GetNumTunings() > 0)
1914 ssb.WriteItem(GetTuneSpecificTunings(), "0", &WriteTuningCollection);
1915 if(AreNonDefaultTuningsUsed(*this))
1916 ssb.WriteItem(*this, "1", &WriteTuningMap);
1917 if(Order().NeedsExtraDatafield())
1918 ssb.WriteItem(Order, "2", &WriteModSequenceOld);
1919 if(needsMptPatSave)
1920 ssb.WriteItem(Patterns, FileIdPatterns, &WriteModPatterns);
1921 ssb.WriteItem(Order, FileIdSequences, &WriteModSequences);
1922
1923 ssb.FinishWrite();
1924
1925 if(ssb.GetStatus() & srlztn::SNT_FAILURE)
1926 {
1927 AddToLog(LogError, U_("Error occurred in writing MPTM extensions."));
1928 }
1929
1930 //Last 4 bytes should tell where the hack mpt things begin.
1931 if(!f.good())
1932 {
1933 f.clear();
1934 success = false;
1935 }
1936 mpt::IO::WriteIntLE<uint32>(f, static_cast<uint32>(MPTStartPos));
1937
1938 mpt::IO::SeekEnd(f);
1939
1940 //END : MPT SPECIFIC
1941
1942 //NO WRITING HERE ANYMORE.
1943
1944 return success;
1945 }
1946
1947
1948 #endif // MODPLUG_NO_FILESAVE
1949
1950
1951 #ifndef MODPLUG_NO_FILESAVE
1952
SaveMixPlugins(std::ostream * file,bool updatePlugData)1953 uint32 CSoundFile::SaveMixPlugins(std::ostream *file, bool updatePlugData)
1954 {
1955 #ifndef NO_PLUGINS
1956 uint32 totalSize = 0;
1957
1958 for(PLUGINDEX i = 0; i < MAX_MIXPLUGINS; i++)
1959 {
1960 const SNDMIXPLUGIN &plugin = m_MixPlugins[i];
1961 if(plugin.IsValidPlugin())
1962 {
1963 uint32 chunkSize = sizeof(SNDMIXPLUGININFO) + 4; // plugininfo+4 (datalen)
1964 if(plugin.pMixPlugin && updatePlugData)
1965 {
1966 plugin.pMixPlugin->SaveAllParameters();
1967 }
1968
1969 const uint32 extraDataSize =
1970 4 + sizeof(float32) + // 4 for ID and size of dryRatio
1971 4 + sizeof(int32); // Default Program
1972 // For each extra entity, add 4 for ID, plus 4 for size of entity, plus size of entity
1973
1974 chunkSize += extraDataSize + 4; // +4 is for size field itself
1975
1976 const uint32 plugDataSize = std::min(mpt::saturate_cast<uint32>(plugin.pluginData.size()), uint32_max - chunkSize);
1977 chunkSize += plugDataSize;
1978
1979 if(file)
1980 {
1981 std::ostream &f = *file;
1982 // Chunk ID (= plugin ID)
1983 char id[4] = { 'F', 'X', '0', '0' };
1984 if(i >= 100) id[1] = '0' + (i / 100u);
1985 id[2] += (i / 10u) % 10u;
1986 id[3] += (i % 10u);
1987 mpt::IO::WriteRaw(f, id, 4);
1988
1989 // Write chunk size, plugin info and plugin data chunk
1990 mpt::IO::WriteIntLE<uint32>(f, chunkSize);
1991 mpt::IO::Write(f, m_MixPlugins[i].Info);
1992 mpt::IO::WriteIntLE<uint32>(f, plugDataSize);
1993 if(plugDataSize)
1994 {
1995 mpt::IO::WriteRaw(f, m_MixPlugins[i].pluginData.data(), plugDataSize);
1996 }
1997
1998 mpt::IO::WriteIntLE<uint32>(f, extraDataSize);
1999
2000 // Dry/Wet ratio
2001 mpt::IO::WriteRaw(f, "DWRT", 4);
2002 // DWRT chunk does not include a size, so better make sure we always write 4 bytes here.
2003 static_assert(sizeof(IEEE754binary32LE) == 4);
2004 mpt::IO::Write(f, IEEE754binary32LE(m_MixPlugins[i].fDryRatio));
2005
2006 // Default program
2007 mpt::IO::WriteRaw(f, "PROG", 4);
2008 // PROG chunk does not include a size, so better make sure we always write 4 bytes here.
2009 static_assert(sizeof(m_MixPlugins[i].defaultProgram) == sizeof(int32));
2010 mpt::IO::WriteIntLE<int32>(f, m_MixPlugins[i].defaultProgram);
2011
2012 // Please, if you add any more chunks here, don't repeat history (see above) and *do* add a size field for your chunk, mmmkay?
2013 }
2014 totalSize += chunkSize + 8;
2015 }
2016 }
2017 std::vector<uint32le> chinfo(GetNumChannels());
2018 uint32 numChInfo = 0;
2019 for(CHANNELINDEX j = 0; j < GetNumChannels(); j++)
2020 {
2021 if((chinfo[j] = ChnSettings[j].nMixPlugin) != 0)
2022 {
2023 numChInfo = j + 1;
2024 }
2025 }
2026 if(numChInfo)
2027 {
2028 if(file)
2029 {
2030 std::ostream &f = *file;
2031 mpt::IO::WriteRaw(f, "CHFX", 4);
2032 mpt::IO::WriteIntLE<uint32>(f, numChInfo * 4);
2033 chinfo.resize(numChInfo);
2034 mpt::IO::Write(f, chinfo);
2035 }
2036 totalSize += numChInfo * 4 + 8;
2037 }
2038 return totalSize;
2039 #else
2040 MPT_UNREFERENCED_PARAMETER(file);
2041 MPT_UNREFERENCED_PARAMETER(updatePlugData);
2042 return 0;
2043 #endif // NO_PLUGINS
2044 }
2045
2046 #endif // MODPLUG_NO_FILESAVE
2047
2048
LoadMixPlugins(FileReader & file)2049 bool CSoundFile::LoadMixPlugins(FileReader &file)
2050 {
2051 bool isBeRoTracker = false;
2052 while(file.CanRead(9))
2053 {
2054 char code[4];
2055 file.ReadArray(code);
2056 const uint32 chunkSize = file.ReadUint32LE();
2057 if(!memcmp(code, "IMPI", 4) // IT instrument, we definitely read too far
2058 || !memcmp(code, "IMPS", 4) // IT sample, ditto
2059 || !memcmp(code, "XTPM", 4) // Instrument extensions, ditto
2060 || !memcmp(code, "STPM", 4) // Song extensions, ditto
2061 || !file.CanRead(chunkSize))
2062 {
2063 file.SkipBack(8);
2064 return isBeRoTracker;
2065 }
2066 FileReader chunk = file.ReadChunk(chunkSize);
2067
2068 // Channel FX
2069 if(!memcmp(code, "CHFX", 4))
2070 {
2071 for(auto &chn : ChnSettings)
2072 {
2073 chn.nMixPlugin = static_cast<PLUGINDEX>(chunk.ReadUint32LE());
2074 }
2075 #ifndef NO_PLUGINS
2076 }
2077 // Plugin Data FX00, ... FX99, F100, ... F255
2078 #define MPT_ISDIGIT(x) (code[(x)] >= '0' && code[(x)] <= '9')
2079 else if(code[0] == 'F' && (code[1] == 'X' || MPT_ISDIGIT(1)) && MPT_ISDIGIT(2) && MPT_ISDIGIT(3))
2080 #undef MPT_ISDIGIT
2081 {
2082 PLUGINDEX plug = (code[2] - '0') * 10 + (code[3] - '0'); //calculate plug-in number.
2083 if(code[1] != 'X') plug += (code[1] - '0') * 100;
2084
2085 if(plug < MAX_MIXPLUGINS)
2086 {
2087 ReadMixPluginChunk(chunk, m_MixPlugins[plug]);
2088 }
2089 #endif // NO_PLUGINS
2090 } else if(!memcmp(code, "MODU", 4))
2091 {
2092 isBeRoTracker = true;
2093 m_dwLastSavedWithVersion = Version(); // Reset MPT detection for old files that have a similar fingerprint
2094 }
2095 }
2096 return isBeRoTracker;
2097 }
2098
2099
2100 #ifndef NO_PLUGINS
ReadMixPluginChunk(FileReader & file,SNDMIXPLUGIN & plugin)2101 void CSoundFile::ReadMixPluginChunk(FileReader &file, SNDMIXPLUGIN &plugin)
2102 {
2103 // MPT's standard plugin data. Size not specified in file.. grrr..
2104 file.ReadStruct(plugin.Info);
2105 mpt::String::SetNullTerminator(plugin.Info.szName.buf);
2106 mpt::String::SetNullTerminator(plugin.Info.szLibraryName.buf);
2107 plugin.editorX = plugin.editorY = int32_min;
2108
2109 // Plugin user data
2110 FileReader pluginDataChunk = file.ReadChunk(file.ReadUint32LE());
2111 plugin.pluginData.resize(mpt::saturate_cast<size_t>(pluginDataChunk.BytesLeft()));
2112 pluginDataChunk.ReadRaw(mpt::as_span(plugin.pluginData));
2113
2114 if(FileReader modularData = file.ReadChunk(file.ReadUint32LE()); modularData.IsValid())
2115 {
2116 while(modularData.CanRead(5))
2117 {
2118 // do we recognize this chunk?
2119 char code[4];
2120 modularData.ReadArray(code);
2121 uint32 dataSize = 0;
2122 if(!memcmp(code, "DWRT", 4) || !memcmp(code, "PROG", 4))
2123 {
2124 // Legacy system with fixed size chunks
2125 dataSize = 4;
2126 } else
2127 {
2128 dataSize = modularData.ReadUint32LE();
2129 }
2130 FileReader dataChunk = modularData.ReadChunk(dataSize);
2131
2132 if(!memcmp(code, "DWRT", 4))
2133 {
2134 plugin.fDryRatio = std::clamp(dataChunk.ReadFloatLE(), 0.0f, 1.0f);
2135 if(!std::isnormal(plugin.fDryRatio))
2136 plugin.fDryRatio = 0.0f;
2137 } else if(!memcmp(code, "PROG", 4))
2138 {
2139 plugin.defaultProgram = dataChunk.ReadUint32LE();
2140 } else if(!memcmp(code, "MCRO", 4))
2141 {
2142 // Read plugin-specific macros
2143 //dataChunk.ReadStructPartial(plugin.macros, dataChunk.GetLength());
2144 }
2145 }
2146 }
2147 }
2148 #endif // NO_PLUGINS
2149
2150
2151 #ifndef MODPLUG_NO_FILESAVE
2152
SaveExtendedSongProperties(std::ostream & f) const2153 void CSoundFile::SaveExtendedSongProperties(std::ostream &f) const
2154 {
2155 const CModSpecifications &specs = GetModSpecifications();
2156 // Extra song data - Yet Another Hack.
2157 mpt::IO::WriteIntLE<uint32>(f, MagicBE("MPTS"));
2158
2159 #define WRITEMODULARHEADER(code, fsize) \
2160 { \
2161 mpt::IO::WriteIntLE<uint32>(f, code); \
2162 MPT_ASSERT(mpt::in_range<uint16>(fsize)); \
2163 const uint16 _size = fsize; \
2164 mpt::IO::WriteIntLE<uint16>(f, _size); \
2165 }
2166 #define WRITEMODULAR(code, field) \
2167 { \
2168 WRITEMODULARHEADER(code, sizeof(field)) \
2169 mpt::IO::WriteIntLE(f, field); \
2170 }
2171
2172 if(m_nDefaultTempo.GetInt() > 255)
2173 {
2174 uint32 tempo = m_nDefaultTempo.GetInt();
2175 WRITEMODULAR(MagicBE("DT.."), tempo);
2176 }
2177 if(m_nDefaultTempo.GetFract() != 0 && specs.hasFractionalTempo)
2178 {
2179 uint32 tempo = m_nDefaultTempo.GetFract();
2180 WRITEMODULAR(MagicLE("DTFR"), tempo);
2181 }
2182
2183 if(m_nDefaultRowsPerBeat > 255 || m_nDefaultRowsPerMeasure > 255 || GetType() == MOD_TYPE_XM)
2184 {
2185 WRITEMODULAR(MagicBE("RPB."), m_nDefaultRowsPerBeat);
2186 WRITEMODULAR(MagicBE("RPM."), m_nDefaultRowsPerMeasure);
2187 }
2188
2189 if(GetType() != MOD_TYPE_XM)
2190 {
2191 WRITEMODULAR(MagicBE("C..."), m_nChannels);
2192 }
2193
2194 if((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && GetNumChannels() > 64)
2195 {
2196 // IT header has only room for 64 channels. Save the settings that do not fit to the header here as an extension.
2197 WRITEMODULARHEADER(MagicBE("ChnS"), (GetNumChannels() - 64) * 2);
2198 for(CHANNELINDEX chn = 64; chn < GetNumChannels(); chn++)
2199 {
2200 uint8 panvol[2];
2201 panvol[0] = (uint8)(ChnSettings[chn].nPan >> 2);
2202 if (ChnSettings[chn].dwFlags[CHN_SURROUND]) panvol[0] = 100;
2203 if (ChnSettings[chn].dwFlags[CHN_MUTE]) panvol[0] |= 0x80;
2204 panvol[1] = (uint8)ChnSettings[chn].nVolume;
2205 mpt::IO::Write(f, panvol);
2206 }
2207 }
2208
2209 {
2210 WRITEMODULARHEADER(MagicBE("TM.."), 1);
2211 uint8 mode = static_cast<uint8>(m_nTempoMode);
2212 mpt::IO::WriteIntLE(f, mode);
2213 }
2214
2215 const int32 tmpMixLevels = static_cast<int32>(m_nMixLevels);
2216 WRITEMODULAR(MagicBE("PMM."), tmpMixLevels);
2217
2218 if(m_dwCreatedWithVersion)
2219 {
2220 WRITEMODULAR(MagicBE("CWV."), m_dwCreatedWithVersion.GetRawVersion());
2221 }
2222
2223 WRITEMODULAR(MagicBE("LSWV"), Version::Current().GetRawVersion());
2224 WRITEMODULAR(MagicBE("SPA."), m_nSamplePreAmp);
2225 WRITEMODULAR(MagicBE("VSTV"), m_nVSTiVolume);
2226
2227 if(GetType() == MOD_TYPE_XM && m_nDefaultGlobalVolume != MAX_GLOBAL_VOLUME)
2228 {
2229 WRITEMODULAR(MagicBE("DGV."), m_nDefaultGlobalVolume);
2230 }
2231
2232 if(GetType() != MOD_TYPE_XM && Order().GetRestartPos() != 0)
2233 {
2234 WRITEMODULAR(MagicBE("RP.."), Order().GetRestartPos());
2235 }
2236
2237 if(m_nResampling != SRCMODE_DEFAULT && specs.hasDefaultResampling)
2238 {
2239 WRITEMODULAR(MagicLE("RSMP"), static_cast<uint32>(m_nResampling));
2240 }
2241
2242 // Sample cues
2243 if(GetType() == MOD_TYPE_MPT)
2244 {
2245 for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
2246 {
2247 const ModSample &sample = Samples[smp];
2248 if(sample.nLength && sample.HasCustomCuePoints())
2249 {
2250 // Write one chunk for every sample.
2251 // Rationale: chunks are limited to 65536 bytes, which can easily be reached
2252 // with the amount of samples that OpenMPT supports.
2253 WRITEMODULARHEADER(MagicLE("CUES"), static_cast<uint16>(2 + std::size(sample.cues) * 4));
2254 mpt::IO::WriteIntLE<uint16>(f, smp);
2255 for(auto cue : sample.cues)
2256 {
2257 mpt::IO::WriteIntLE<uint32>(f, cue);
2258 }
2259 }
2260 }
2261 }
2262
2263 // Tempo Swing Factors
2264 if(!m_tempoSwing.empty())
2265 {
2266 std::ostringstream oStrm;
2267 TempoSwing::Serialize(oStrm, m_tempoSwing);
2268 std::string data = oStrm.str();
2269 uint16 length = mpt::saturate_cast<uint16>(data.size());
2270 WRITEMODULARHEADER(MagicLE("SWNG"), length);
2271 mpt::IO::WriteRaw(f, data.data(), length);
2272 }
2273
2274 // Playback compatibility flags
2275 {
2276 uint8 bits[(kMaxPlayBehaviours + 7) / 8u];
2277 MemsetZero(bits);
2278 size_t maxBit = 0;
2279 for(size_t i = 0; i < kMaxPlayBehaviours; i++)
2280 {
2281 if(m_playBehaviour[i])
2282 {
2283 bits[i >> 3] |= 1 << (i & 0x07);
2284 maxBit = i + 8;
2285 }
2286 }
2287 uint16 numBytes = static_cast<uint16>(maxBit / 8u);
2288 WRITEMODULARHEADER(MagicBE("MSF."), numBytes);
2289 mpt::IO::WriteRaw(f, bits, numBytes);
2290 }
2291
2292 if(!m_songArtist.empty() && specs.hasArtistName)
2293 {
2294 std::string songArtistU8 = mpt::ToCharset(mpt::Charset::UTF8, m_songArtist);
2295 uint16 length = mpt::saturate_cast<uint16>(songArtistU8.length());
2296 WRITEMODULARHEADER(MagicLE("AUTH"), length);
2297 mpt::IO::WriteRaw(f, songArtistU8.c_str(), length);
2298 }
2299
2300 #ifdef MODPLUG_TRACKER
2301 // MIDI mapping directives
2302 if(GetMIDIMapper().GetCount() > 0)
2303 {
2304 const size_t objectsize = GetMIDIMapper().Serialize();
2305 if(!mpt::in_range<uint16>(objectsize))
2306 {
2307 AddToLog(LogWarning, U_("Too many MIDI Mapping directives to save; data won't be written."));
2308 } else
2309 {
2310 WRITEMODULARHEADER(MagicBE("MIMA"), static_cast<uint16>(objectsize));
2311 GetMIDIMapper().Serialize(&f);
2312 }
2313 }
2314
2315 // Channel colors
2316 {
2317 CHANNELINDEX numChannels = 0;
2318 for(CHANNELINDEX i = 0; i < m_nChannels; i++)
2319 {
2320 if(ChnSettings[i].color != ModChannelSettings::INVALID_COLOR)
2321 {
2322 numChannels = i + 1;
2323 }
2324 }
2325 if(numChannels > 0)
2326 {
2327 WRITEMODULARHEADER(MagicLE("CCOL"), numChannels * 4);
2328 for(CHANNELINDEX i = 0; i < numChannels; i++)
2329 {
2330 uint32 color = ChnSettings[i].color;
2331 if(color != ModChannelSettings::INVALID_COLOR)
2332 color &= 0x00FFFFFF;
2333 std::array<uint8, 4> rgb{static_cast<uint8>(color), static_cast<uint8>(color >> 8), static_cast<uint8>(color >> 16), static_cast<uint8>(color >> 24)};
2334 mpt::IO::Write(f, rgb);
2335 }
2336 }
2337 }
2338 #endif
2339
2340 #undef WRITEMODULAR
2341 #undef WRITEMODULARHEADER
2342 return;
2343 }
2344
2345 #endif // MODPLUG_NO_FILESAVE
2346
2347
2348 template<typename T>
ReadField(FileReader & chunk,std::size_t size,T & field)2349 void ReadField(FileReader &chunk, std::size_t size, T &field)
2350 {
2351 field = chunk.ReadSizedIntLE<T>(size);
2352 }
2353
2354
2355 template<typename T>
ReadFieldCast(FileReader & chunk,std::size_t size,T & field)2356 void ReadFieldCast(FileReader &chunk, std::size_t size, T &field)
2357 {
2358 static_assert(sizeof(T) <= sizeof(int32));
2359 field = static_cast<T>(chunk.ReadSizedIntLE<int32>(size));
2360 }
2361
2362
LoadExtendedSongProperties(FileReader & file,bool ignoreChannelCount,bool * pInterpretMptMade)2363 void CSoundFile::LoadExtendedSongProperties(FileReader &file, bool ignoreChannelCount, bool *pInterpretMptMade)
2364 {
2365 if(!file.ReadMagic("STPM")) // 'MPTS'
2366 {
2367 return;
2368 }
2369
2370 // Found MPTS, interpret the file MPT made.
2371 if(pInterpretMptMade != nullptr)
2372 *pInterpretMptMade = true;
2373
2374 // HACK: Reset mod flags to default values here, as they are not always written.
2375 m_playBehaviour.reset();
2376
2377 while(file.CanRead(7))
2378 {
2379 const uint32 code = file.ReadUint32LE();
2380 const uint16 size = file.ReadUint16LE();
2381
2382 // Start of MPTM extensions, non-ASCII ID or truncated field
2383 if(code == MagicLE("228\x04"))
2384 {
2385 file.SkipBack(6);
2386 break;
2387 } else if((code & 0x80808080) || !(code & 0x60606060) || !file.CanRead(size))
2388 {
2389 break;
2390 }
2391
2392 FileReader chunk = file.ReadChunk(size);
2393
2394 switch (code) // interpret field code
2395 {
2396 case MagicBE("DT.."): { uint32 tempo; ReadField(chunk, size, tempo); m_nDefaultTempo.Set(tempo, m_nDefaultTempo.GetFract()); break; }
2397 case MagicLE("DTFR"): { uint32 tempoFract; ReadField(chunk, size, tempoFract); m_nDefaultTempo.Set(m_nDefaultTempo.GetInt(), tempoFract); break; }
2398 case MagicBE("RPB."): ReadField(chunk, size, m_nDefaultRowsPerBeat); break;
2399 case MagicBE("RPM."): ReadField(chunk, size, m_nDefaultRowsPerMeasure); break;
2400 // FIXME: If there are only PC events on the last few channels in an MPTM MO3, they won't be imported!
2401 case MagicBE("C..."): if(!ignoreChannelCount) { CHANNELINDEX chn = 0; ReadField(chunk, size, chn); m_nChannels = Clamp(chn, m_nChannels, MAX_BASECHANNELS); } break;
2402 case MagicBE("TM.."): ReadFieldCast(chunk, size, m_nTempoMode); break;
2403 case MagicBE("PMM."): ReadFieldCast(chunk, size, m_nMixLevels); break;
2404 case MagicBE("CWV."): { uint32 ver = 0; ReadField(chunk, size, ver); m_dwCreatedWithVersion = Version(ver); break; }
2405 case MagicBE("LSWV"): { uint32 ver = 0; ReadField(chunk, size, ver); if(ver != 0) { m_dwLastSavedWithVersion = Version(ver); } break; }
2406 case MagicBE("SPA."): ReadField(chunk, size, m_nSamplePreAmp); break;
2407 case MagicBE("VSTV"): ReadField(chunk, size, m_nVSTiVolume); break;
2408 case MagicBE("DGV."): ReadField(chunk, size, m_nDefaultGlobalVolume); break;
2409 case MagicBE("RP.."): if(GetType() != MOD_TYPE_XM) { ORDERINDEX restartPos; ReadField(chunk, size, restartPos); Order().SetRestartPos(restartPos); } break;
2410 case MagicLE("RSMP"):
2411 ReadFieldCast(chunk, size, m_nResampling);
2412 if(!Resampling::IsKnownMode(m_nResampling)) m_nResampling = SRCMODE_DEFAULT;
2413 break;
2414 #ifdef MODPLUG_TRACKER
2415 case MagicBE("MIMA"): GetMIDIMapper().Deserialize(chunk); break;
2416
2417 case MagicLE("CCOL"):
2418 // Channel colors
2419 {
2420 const CHANNELINDEX numChannels = std::min(MAX_BASECHANNELS, static_cast<CHANNELINDEX>(size / 4u));
2421 for(CHANNELINDEX i = 0; i < numChannels; i++)
2422 {
2423 auto rgb = chunk.ReadArray<uint8, 4>();
2424 if(rgb[3])
2425 ChnSettings[i].color = ModChannelSettings::INVALID_COLOR;
2426 else
2427 ChnSettings[i].color = rgb[0] | (rgb[1] << 8) | (rgb[2] << 16);
2428 }
2429 }
2430 break;
2431 #endif
2432 case MagicLE("AUTH"):
2433 {
2434 std::string artist;
2435 chunk.ReadString<mpt::String::spacePadded>(artist, chunk.GetLength());
2436 m_songArtist = mpt::ToUnicode(mpt::Charset::UTF8, artist);
2437 }
2438 break;
2439 case MagicBE("ChnS"):
2440 // Channel settings for channels 65+
2441 if(size <= (MAX_BASECHANNELS - 64) * 2 && (size % 2u) == 0)
2442 {
2443 static_assert(mpt::array_size<decltype(ChnSettings)>::size >= 64);
2444 const CHANNELINDEX loopLimit = std::min(uint16(64 + size / 2), uint16(std::size(ChnSettings)));
2445
2446 for(CHANNELINDEX chn = 64; chn < loopLimit; chn++)
2447 {
2448 auto [pan, vol] = chunk.ReadArray<uint8, 2>();
2449 if(pan != 0xFF)
2450 {
2451 ChnSettings[chn].nVolume = vol;
2452 ChnSettings[chn].nPan = 128;
2453 ChnSettings[chn].dwFlags.reset();
2454 if(pan & 0x80) ChnSettings[chn].dwFlags.set(CHN_MUTE);
2455 pan &= 0x7F;
2456 if(pan <= 64) ChnSettings[chn].nPan = pan << 2;
2457 if(pan == 100) ChnSettings[chn].dwFlags.set(CHN_SURROUND);
2458 }
2459 }
2460 }
2461 break;
2462
2463 case MagicLE("CUES"):
2464 // Sample cues
2465 if(size > 2)
2466 {
2467 SAMPLEINDEX smp = chunk.ReadUint16LE();
2468 if(smp > 0 && smp <= GetNumSamples())
2469 {
2470 ModSample &sample = Samples[smp];
2471 for(auto &cue : sample.cues)
2472 {
2473 cue = chunk.ReadUint32LE();
2474 }
2475 }
2476 }
2477 break;
2478
2479 case MagicLE("SWNG"):
2480 // Tempo Swing Factors
2481 if(size > 2)
2482 {
2483 std::istringstream iStrm(mpt::buffer_cast<std::string>(chunk.ReadRawDataAsByteVector()));
2484 TempoSwing::Deserialize(iStrm, m_tempoSwing, chunk.GetLength());
2485 }
2486 break;
2487
2488 case MagicBE("MSF."):
2489 // Playback compatibility flags
2490 {
2491 size_t bit = 0;
2492 m_playBehaviour.reset();
2493 while(chunk.CanRead(1) && bit < m_playBehaviour.size())
2494 {
2495 uint8 b = chunk.ReadUint8();
2496 for(uint8 i = 0; i < 8; i++, bit++)
2497 {
2498 if((b & (1 << i)) && bit < m_playBehaviour.size())
2499 {
2500 m_playBehaviour.set(bit);
2501 }
2502 }
2503 }
2504 }
2505 break;
2506 }
2507 }
2508
2509 // Validate read values.
2510 Limit(m_nDefaultTempo, GetModSpecifications().GetTempoMin(), GetModSpecifications().GetTempoMax());
2511 if(m_nTempoMode >= TempoMode::NumModes)
2512 m_nTempoMode = TempoMode::Classic;
2513 if(m_nMixLevels >= MixLevels::NumMixLevels)
2514 m_nMixLevels = MixLevels::Original;
2515 //m_dwCreatedWithVersion
2516 //m_dwLastSavedWithVersion
2517 //m_nSamplePreAmp
2518 //m_nVSTiVolume
2519 //m_nDefaultGlobalVolume
2520 LimitMax(m_nDefaultGlobalVolume, MAX_GLOBAL_VOLUME);
2521 //m_nRestartPos
2522 //m_ModFlags
2523 LimitMax(m_nDefaultRowsPerBeat, MAX_ROWS_PER_BEAT);
2524 LimitMax(m_nDefaultRowsPerMeasure, MAX_ROWS_PER_BEAT);
2525 }
2526
2527
2528 OPENMPT_NAMESPACE_END
2529