1 /*
2 * Load_xm.cpp
3 * -----------
4 * Purpose: XM (FastTracker II) module loader / saver
5 * Notes : (currently none)
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 "../common/version.h"
15 #include "XMTools.h"
16 #include "mod_specifications.h"
17 #ifndef MODPLUG_NO_FILESAVE
18 #include "mpt/io/base.hpp"
19 #include "mpt/io/io.hpp"
20 #include "mpt/io/io_stdstream.hpp"
21 #include "../common/mptFileIO.h"
22 #endif
23 #include "OggStream.h"
24 #include <algorithm>
25 #ifdef MODPLUG_TRACKER
26 #include "../mptrack/TrackerSettings.h" // For super smooth ramping option
27 #endif // MODPLUG_TRACKER
28 #include "mpt/audio/span.hpp"
29
30 #if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE)
31 #include <sstream>
32 #endif
33
34 #if defined(MPT_WITH_VORBIS)
35 #if MPT_COMPILER_CLANG
36 #pragma clang diagnostic push
37 #pragma clang diagnostic ignored "-Wreserved-id-macro"
38 #endif // MPT_COMPILER_CLANG
39 #include <vorbis/codec.h>
40 #if MPT_COMPILER_CLANG
41 #pragma clang diagnostic pop
42 #endif // MPT_COMPILER_CLANG
43 #endif
44
45 #if defined(MPT_WITH_VORBISFILE)
46 #if MPT_COMPILER_CLANG
47 #pragma clang diagnostic push
48 #pragma clang diagnostic ignored "-Wreserved-id-macro"
49 #endif // MPT_COMPILER_CLANG
50 #include <vorbis/vorbisfile.h>
51 #if MPT_COMPILER_CLANG
52 #pragma clang diagnostic pop
53 #endif // MPT_COMPILER_CLANG
54 #include "openmpt/soundbase/Copy.hpp"
55 #endif
56
57 #ifdef MPT_WITH_STBVORBIS
58 #include <stb_vorbis/stb_vorbis.c>
59 #include "openmpt/soundbase/Copy.hpp"
60 #endif // MPT_WITH_STBVORBIS
61
62
63 OPENMPT_NAMESPACE_BEGIN
64
65
66
67 #if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE)
68
VorbisfileFilereaderRead(void * ptr,size_t size,size_t nmemb,void * datasource)69 static size_t VorbisfileFilereaderRead(void *ptr, size_t size, size_t nmemb, void *datasource)
70 {
71 FileReader &file = *reinterpret_cast<FileReader*>(datasource);
72 return file.ReadRaw(mpt::span(mpt::void_cast<std::byte*>(ptr), size * nmemb)).size() / size;
73 }
74
VorbisfileFilereaderSeek(void * datasource,ogg_int64_t offset,int whence)75 static int VorbisfileFilereaderSeek(void *datasource, ogg_int64_t offset, int whence)
76 {
77 FileReader &file = *reinterpret_cast<FileReader*>(datasource);
78 switch(whence)
79 {
80 case SEEK_SET:
81 {
82 if(!mpt::in_range<FileReader::off_t>(offset))
83 {
84 return -1;
85 }
86 return file.Seek(mpt::saturate_cast<FileReader::off_t>(offset)) ? 0 : -1;
87 }
88 break;
89 case SEEK_CUR:
90 {
91 if(offset < 0)
92 {
93 if(offset == std::numeric_limits<ogg_int64_t>::min())
94 {
95 return -1;
96 }
97 if(!mpt::in_range<FileReader::off_t>(0-offset))
98 {
99 return -1;
100 }
101 return file.SkipBack(mpt::saturate_cast<FileReader::off_t>(0 - offset)) ? 0 : -1;
102 } else
103 {
104 if(!mpt::in_range<FileReader::off_t>(offset))
105 {
106 return -1;
107 }
108 return file.Skip(mpt::saturate_cast<FileReader::off_t>(offset)) ? 0 : -1;
109 }
110 }
111 break;
112 case SEEK_END:
113 {
114 if(!mpt::in_range<FileReader::off_t>(offset))
115 {
116 return -1;
117 }
118 if(!mpt::in_range<FileReader::off_t>(file.GetLength() + offset))
119 {
120 return -1;
121 }
122 return file.Seek(mpt::saturate_cast<FileReader::off_t>(file.GetLength() + offset)) ? 0 : -1;
123 }
124 break;
125 default:
126 return -1;
127 }
128 }
129
VorbisfileFilereaderTell(void * datasource)130 static long VorbisfileFilereaderTell(void *datasource)
131 {
132 FileReader &file = *reinterpret_cast<FileReader*>(datasource);
133 FileReader::off_t result = file.GetPosition();
134 if(!mpt::in_range<long>(result))
135 {
136 return -1;
137 }
138 return static_cast<long>(result);
139 }
140
141 #endif // MPT_WITH_VORBIS && MPT_WITH_VORBISFILE
142
143
144 // Allocate samples for an instrument
AllocateXMSamples(CSoundFile & sndFile,SAMPLEINDEX numSamples)145 static std::vector<SAMPLEINDEX> AllocateXMSamples(CSoundFile &sndFile, SAMPLEINDEX numSamples)
146 {
147 LimitMax(numSamples, SAMPLEINDEX(32));
148
149 std::vector<SAMPLEINDEX> foundSlots;
150 foundSlots.reserve(numSamples);
151
152 for(SAMPLEINDEX i = 0; i < numSamples; i++)
153 {
154 SAMPLEINDEX candidateSlot = sndFile.GetNumSamples() + 1;
155
156 if(candidateSlot >= MAX_SAMPLES)
157 {
158 // If too many sample slots are needed, try to fill some empty slots first.
159 for(SAMPLEINDEX j = 1; j <= sndFile.GetNumSamples(); j++)
160 {
161 if(sndFile.GetSample(j).HasSampleData())
162 {
163 continue;
164 }
165
166 if(!mpt::contains(foundSlots, j))
167 {
168 // Empty sample slot that is not occupied by the current instrument. Yay!
169 candidateSlot = j;
170
171 // Remove unused sample from instrument sample assignments
172 for(INSTRUMENTINDEX ins = 1; ins <= sndFile.GetNumInstruments(); ins++)
173 {
174 if(sndFile.Instruments[ins] == nullptr)
175 {
176 continue;
177 }
178 for(auto &sample : sndFile.Instruments[ins]->Keyboard)
179 {
180 if(sample == candidateSlot)
181 {
182 sample = 0;
183 }
184 }
185 }
186 break;
187 }
188 }
189 }
190
191 if(candidateSlot >= MAX_SAMPLES)
192 {
193 // Still couldn't find any empty sample slots, so look out for existing but unused samples.
194 std::vector<bool> usedSamples;
195 SAMPLEINDEX unusedSampleCount = sndFile.DetectUnusedSamples(usedSamples);
196
197 if(unusedSampleCount > 0)
198 {
199 sndFile.RemoveSelectedSamples(usedSamples);
200 // Remove unused samples from instrument sample assignments
201 for(INSTRUMENTINDEX ins = 1; ins <= sndFile.GetNumInstruments(); ins++)
202 {
203 if(sndFile.Instruments[ins] == nullptr)
204 {
205 continue;
206 }
207 for(auto &sample : sndFile.Instruments[ins]->Keyboard)
208 {
209 if(sample < usedSamples.size() && !usedSamples[sample])
210 {
211 sample = 0;
212 }
213 }
214 }
215
216 // New candidate slot is first unused sample slot.
217 candidateSlot = static_cast<SAMPLEINDEX>(std::find(usedSamples.begin() + 1, usedSamples.end(), false) - usedSamples.begin());
218 } else
219 {
220 // No unused sampel slots: Give up :(
221 break;
222 }
223 }
224
225 if(candidateSlot < MAX_SAMPLES)
226 {
227 foundSlots.push_back(candidateSlot);
228 if(candidateSlot > sndFile.GetNumSamples())
229 {
230 sndFile.m_nSamples = candidateSlot;
231 }
232 }
233 }
234
235 return foundSlots;
236 }
237
238
239 // Read .XM patterns
ReadXMPatterns(FileReader & file,const XMFileHeader & fileHeader,CSoundFile & sndFile)240 static void ReadXMPatterns(FileReader &file, const XMFileHeader &fileHeader, CSoundFile &sndFile)
241 {
242 // Reading patterns
243 sndFile.Patterns.ResizeArray(fileHeader.patterns);
244 for(PATTERNINDEX pat = 0; pat < fileHeader.patterns; pat++)
245 {
246 FileReader::off_t curPos = file.GetPosition();
247 uint32 headerSize = file.ReadUint32LE();
248 file.Skip(1); // Pack method (= 0)
249
250 ROWINDEX numRows = 64;
251
252 if(fileHeader.version == 0x0102)
253 {
254 numRows = file.ReadUint8() + 1;
255 } else
256 {
257 numRows = file.ReadUint16LE();
258 }
259
260 // A packed size of 0 indicates a completely empty pattern.
261 const uint16 packedSize = file.ReadUint16LE();
262
263 if(numRows == 0)
264 numRows = 64;
265 else if(numRows > MAX_PATTERN_ROWS)
266 numRows = MAX_PATTERN_ROWS;
267
268 file.Seek(curPos + headerSize);
269 FileReader patternChunk = file.ReadChunk(packedSize);
270
271 if(!sndFile.Patterns.Insert(pat, numRows) || packedSize == 0)
272 {
273 continue;
274 }
275
276 enum PatternFlags
277 {
278 isPackByte = 0x80,
279 allFlags = 0xFF,
280
281 notePresent = 0x01,
282 instrPresent = 0x02,
283 volPresent = 0x04,
284 commandPresent = 0x08,
285 paramPresent = 0x10,
286 };
287
288 for(auto &m : sndFile.Patterns[pat])
289 {
290 uint8 info = patternChunk.ReadUint8();
291
292 uint8 vol = 0;
293 if(info & isPackByte)
294 {
295 // Interpret byte as flag set.
296 if(info & notePresent) m.note = patternChunk.ReadUint8();
297 } else
298 {
299 // Interpret byte as note, read all other pattern fields as well.
300 m.note = info;
301 info = allFlags;
302 }
303
304 if(info & instrPresent) m.instr = patternChunk.ReadUint8();
305 if(info & volPresent) vol = patternChunk.ReadUint8();
306 if(info & commandPresent) m.command = patternChunk.ReadUint8();
307 if(info & paramPresent) m.param = patternChunk.ReadUint8();
308
309 if(m.note == 97)
310 {
311 m.note = NOTE_KEYOFF;
312 } else if(m.note > 0 && m.note < 97)
313 {
314 m.note += 12;
315 } else
316 {
317 m.note = NOTE_NONE;
318 }
319
320 if(m.command | m.param)
321 {
322 CSoundFile::ConvertModCommand(m);
323 } else
324 {
325 m.command = CMD_NONE;
326 }
327
328 if(m.instr == 0xFF)
329 {
330 m.instr = 0;
331 }
332
333 if(vol >= 0x10 && vol <= 0x50)
334 {
335 m.volcmd = VOLCMD_VOLUME;
336 m.vol = vol - 0x10;
337 } else if (vol >= 0x60)
338 {
339 // Volume commands 6-F translation.
340 static constexpr ModCommand::VOLCMD volEffTrans[] =
341 {
342 VOLCMD_VOLSLIDEDOWN, VOLCMD_VOLSLIDEUP, VOLCMD_FINEVOLDOWN, VOLCMD_FINEVOLUP,
343 VOLCMD_VIBRATOSPEED, VOLCMD_VIBRATODEPTH, VOLCMD_PANNING, VOLCMD_PANSLIDELEFT,
344 VOLCMD_PANSLIDERIGHT, VOLCMD_TONEPORTAMENTO,
345 };
346
347 m.volcmd = volEffTrans[(vol - 0x60) >> 4];
348 m.vol = vol & 0x0F;
349
350 if(m.volcmd == VOLCMD_PANNING)
351 {
352 m.vol *= 4; // FT2 does indeed not scale panning symmetrically.
353 }
354 }
355 }
356 }
357 }
358
359
360 enum TrackerVersions
361 {
362 verUnknown = 0x00, // Probably not made with MPT
363 verOldModPlug = 0x01, // Made with MPT Alpha / Beta
364 verNewModPlug = 0x02, // Made with MPT (not Alpha / Beta)
365 verModPlug1_09 = 0x04, // Made with MPT 1.09 or possibly other version
366 verOpenMPT = 0x08, // Made with OpenMPT
367 verConfirmed = 0x10, // We are very sure that we found the correct tracker version.
368
369 verFT2Generic = 0x20, // "FastTracker v2.00", but FastTracker has NOT been ruled out
370 verOther = 0x40, // Something we don't know, testing for DigiTrakker.
371 verFT2Clone = 0x80, // NOT FT2: itype changed between instruments, or \0 found in song title
372 verDigiTrakker = 0x100, // Probably DigiTrakker
373 verUNMO3 = 0x200, // TODO: UNMO3-ed XMs are detected as MPT 1.16
374 verEmptyOrders = 0x400, // Allow empty order list like in OpenMPT (FT2 just plays pattern 0 if the order list is empty according to the header)
375 };
DECLARE_FLAGSET(TrackerVersions)376 DECLARE_FLAGSET(TrackerVersions)
377
378
379 static bool ValidateHeader(const XMFileHeader &fileHeader)
380 {
381 if(fileHeader.channels == 0
382 || fileHeader.channels > MAX_BASECHANNELS
383 || std::memcmp(fileHeader.signature, "Extended Module: ", 17)
384 )
385 {
386 return false;
387 }
388 return true;
389 }
390
391
GetHeaderMinimumAdditionalSize(const XMFileHeader & fileHeader)392 static uint64 GetHeaderMinimumAdditionalSize(const XMFileHeader &fileHeader)
393 {
394 return fileHeader.orders + 4 * (fileHeader.patterns + fileHeader.instruments);
395 }
396
397
ProbeFileHeaderXM(MemoryFileReader file,const uint64 * pfilesize)398 CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderXM(MemoryFileReader file, const uint64 *pfilesize)
399 {
400 XMFileHeader fileHeader;
401 if(!file.ReadStruct(fileHeader))
402 {
403 return ProbeWantMoreData;
404 }
405 if(!ValidateHeader(fileHeader))
406 {
407 return ProbeFailure;
408 }
409 return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
410 }
411
412
ReadSampleData(ModSample & sample,SampleIO sampleFlags,FileReader & sampleChunk,bool & isOXM)413 static bool ReadSampleData(ModSample &sample, SampleIO sampleFlags, FileReader &sampleChunk, bool &isOXM)
414 {
415 bool unsupportedSample = false;
416
417 bool isOGG = false;
418 if(sampleChunk.CanRead(8))
419 {
420 isOGG = true;
421 sampleChunk.Skip(4);
422 // In order to avoid false-detecting PCM as OggVorbis as much as possible,
423 // we parse and verify the complete sample data and only assume OggVorbis,
424 // if all Ogg checksums are correct a no single byte of non-Ogg data exists.
425 // The fast-path for regular PCM will only check "OggS" magic and do no other work after failing that check.
426 while(!sampleChunk.EndOfFile())
427 {
428 if(!Ogg::ReadPage(sampleChunk))
429 {
430 isOGG = false;
431 break;
432 }
433 }
434 }
435 isOXM = isOXM || isOGG;
436 sampleChunk.Rewind();
437 if(isOGG)
438 {
439 uint32 originalSize = sampleChunk.ReadInt32LE();
440 FileReader sampleData = sampleChunk.ReadChunk(sampleChunk.BytesLeft());
441
442 sample.uFlags.set(CHN_16BIT, sampleFlags.GetBitDepth() >= 16);
443 sample.uFlags.set(CHN_STEREO, sampleFlags.GetChannelFormat() != SampleIO::mono);
444 sample.nLength = originalSize / (sample.uFlags[CHN_16BIT] ? 2 : 1) / (sample.uFlags[CHN_STEREO] ? 2 : 1);
445
446 #if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE)
447
448 ov_callbacks callbacks = {
449 &VorbisfileFilereaderRead,
450 &VorbisfileFilereaderSeek,
451 NULL,
452 &VorbisfileFilereaderTell
453 };
454 OggVorbis_File vf;
455 MemsetZero(vf);
456 if(ov_open_callbacks(&sampleData, &vf, nullptr, 0, callbacks) == 0)
457 {
458 if(ov_streams(&vf) == 1)
459 { // we do not support chained vorbis samples
460 vorbis_info *vi = ov_info(&vf, -1);
461 if(vi && vi->rate > 0 && vi->channels > 0)
462 {
463 sample.AllocateSample();
464 SmpLength offset = 0;
465 int channels = vi->channels;
466 int current_section = 0;
467 long decodedSamples = 0;
468 bool eof = false;
469 while(!eof && offset < sample.nLength && sample.HasSampleData())
470 {
471 float **output = nullptr;
472 long ret = ov_read_float(&vf, &output, 1024, ¤t_section);
473 if(ret == 0)
474 {
475 eof = true;
476 } else if(ret < 0)
477 {
478 // stream error, just try to continue
479 } else
480 {
481 decodedSamples = ret;
482 LimitMax(decodedSamples, mpt::saturate_cast<long>(sample.nLength - offset));
483 if(decodedSamples > 0 && channels == sample.GetNumChannels())
484 {
485 if(sample.uFlags[CHN_16BIT])
486 {
487 CopyAudio(mpt::audio_span_interleaved(sample.sample16() + (offset * sample.GetNumChannels()), sample.GetNumChannels(), decodedSamples), mpt::audio_span_planar(output, channels, decodedSamples));
488 } else
489 {
490 CopyAudio(mpt::audio_span_interleaved(sample.sample8() + (offset * sample.GetNumChannels()), sample.GetNumChannels(), decodedSamples), mpt::audio_span_planar(output, channels, decodedSamples));
491 }
492 }
493 offset += decodedSamples;
494 }
495 }
496 } else
497 {
498 unsupportedSample = true;
499 }
500 } else
501 {
502 unsupportedSample = true;
503 }
504 ov_clear(&vf);
505 } else
506 {
507 unsupportedSample = true;
508 }
509
510 #elif defined(MPT_WITH_STBVORBIS)
511
512 // NOTE/TODO: stb_vorbis does not handle inferred negative PCM sample
513 // position at stream start. (See
514 // <https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-132000A.2>).
515 // This means that, for remuxed and re-aligned/cutted (at stream start)
516 // Vorbis files, stb_vorbis will include superfluous samples at the
517 // beginning. OXM files with this property are yet to be spotted in the
518 // wild, thus, this behaviour is currently not problematic.
519
520 int consumed = 0, error = 0;
521 stb_vorbis *vorb = nullptr;
522 FileReader::PinnedView sampleDataView = sampleData.GetPinnedView();
523 const std::byte* data = sampleDataView.data();
524 std::size_t dataLeft = sampleDataView.size();
525 vorb = stb_vorbis_open_pushdata(mpt::byte_cast<const unsigned char*>(data), mpt::saturate_cast<int>(dataLeft), &consumed, &error, nullptr);
526 sampleData.Skip(consumed);
527 data += consumed;
528 dataLeft -= consumed;
529 if(vorb)
530 {
531 // Header has been read, proceed to reading the sample data
532 sample.AllocateSample();
533 SmpLength offset = 0;
534 while((error == VORBIS__no_error || (error == VORBIS_need_more_data && dataLeft > 0))
535 && offset < sample.nLength && sample.HasSampleData())
536 {
537 int channels = 0, decodedSamples = 0;
538 float **output;
539 consumed = stb_vorbis_decode_frame_pushdata(vorb, mpt::byte_cast<const unsigned char*>(data), mpt::saturate_cast<int>(dataLeft), &channels, &output, &decodedSamples);
540 sampleData.Skip(consumed);
541 data += consumed;
542 dataLeft -= consumed;
543 LimitMax(decodedSamples, mpt::saturate_cast<int>(sample.nLength - offset));
544 if(decodedSamples > 0 && channels == sample.GetNumChannels())
545 {
546 if(sample.uFlags[CHN_16BIT])
547 {
548 CopyAudio(mpt::audio_span_interleaved(sample.sample16() + (offset * sample.GetNumChannels()), sample.GetNumChannels(), decodedSamples), mpt::audio_span_planar(output, channels, decodedSamples));
549 } else
550 {
551 CopyAudio(mpt::audio_span_interleaved(sample.sample8() + (offset * sample.GetNumChannels()), sample.GetNumChannels(), decodedSamples), mpt::audio_span_planar(output, channels, decodedSamples));
552 }
553 }
554 offset += decodedSamples;
555 error = stb_vorbis_get_error(vorb);
556 }
557 stb_vorbis_close(vorb);
558 } else
559 {
560 unsupportedSample = true;
561 }
562
563 #else // !VORBIS
564
565 unsupportedSample = true;
566
567 #endif // VORBIS
568
569 } else
570 {
571 sampleFlags.ReadSample(sample, sampleChunk);
572 }
573
574 return !unsupportedSample;
575 }
576
577
ReadXM(FileReader & file,ModLoadingFlags loadFlags)578 bool CSoundFile::ReadXM(FileReader &file, ModLoadingFlags loadFlags)
579 {
580 file.Rewind();
581
582 XMFileHeader fileHeader;
583 if(!file.ReadStruct(fileHeader))
584 {
585 return false;
586 }
587 if(!ValidateHeader(fileHeader))
588 {
589 return false;
590 }
591 if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
592 {
593 return false;
594 } else if(loadFlags == onlyVerifyHeader)
595 {
596 return true;
597 }
598
599 InitializeGlobals(MOD_TYPE_XM);
600 InitializeChannels();
601 m_nMixLevels = MixLevels::Compatible;
602
603 FlagSet<TrackerVersions> madeWith(verUnknown);
604 mpt::ustring madeWithTracker;
605 bool isMadTracker = false;
606
607 if(!memcmp(fileHeader.trackerName, "FastTracker v2.00 ", 20) && fileHeader.size == 276)
608 {
609 if(fileHeader.version < 0x0104)
610 madeWith = verFT2Generic | verConfirmed;
611 else if(memchr(fileHeader.songName, '\0', 20) != nullptr)
612 // FT2 pads the song title with spaces, some other trackers use null chars
613 madeWith = verFT2Clone | verNewModPlug | verEmptyOrders;
614 else
615 madeWith = verFT2Generic | verNewModPlug;
616 } else if(!memcmp(fileHeader.trackerName, "FastTracker v 2.00 ", 20))
617 {
618 // MPT 1.0 (exact version to be determined later)
619 madeWith = verOldModPlug;
620 } else
621 {
622 // Something else!
623 madeWith = verUnknown | verConfirmed;
624
625 madeWithTracker = mpt::ToUnicode(mpt::Charset::CP437, mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.trackerName));
626
627 if(!memcmp(fileHeader.trackerName, "OpenMPT ", 8))
628 {
629 madeWith = verOpenMPT | verConfirmed | verEmptyOrders;
630 } else if(!memcmp(fileHeader.trackerName, "MilkyTracker ", 12))
631 {
632 // MilkyTracker prior to version 0.90.87 doesn't set a version string.
633 // Luckily, starting with v0.90.87, MilkyTracker also implements the FT2 panning scheme.
634 if(memcmp(fileHeader.trackerName + 12, " ", 8))
635 {
636 m_nMixLevels = MixLevels::CompatibleFT2;
637 }
638 } else if(!memcmp(fileHeader.trackerName, "Fasttracker II clone", 20))
639 {
640 // 8bitbubsy's FT2 clone should be treated exactly like FT2
641 madeWith = verFT2Generic | verConfirmed;
642 } else if(!memcmp(fileHeader.trackerName, "MadTracker 2.0\0", 15))
643 {
644 // Fix channel 2 in m3_cha.xm
645 m_playBehaviour.reset(kFT2PortaNoNote);
646 // Fix arpeggios in kragle_-_happy_day.xm
647 m_playBehaviour.reset(kFT2Arpeggio);
648 isMadTracker = true;
649 } else if(!memcmp(fileHeader.trackerName, "Skale Tracker\0", 14) || !memcmp(fileHeader.trackerName, "Sk@le Tracker\0", 14))
650 {
651 m_playBehaviour.reset(kFT2ST3OffsetOutOfRange);
652 // Fix arpeggios in KAPTENFL.XM
653 m_playBehaviour.reset(kFT2Arpeggio);
654 } else if(!memcmp(fileHeader.trackerName, "*Converted ", 11))
655 {
656 madeWith = verDigiTrakker;
657 }
658 }
659
660 m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.songName);
661
662 m_nMinPeriod = 1;
663 m_nMaxPeriod = 31999;
664
665 Order().SetRestartPos(fileHeader.restartPos);
666 m_nChannels = fileHeader.channels;
667 m_nInstruments = std::min(static_cast<uint16>(fileHeader.instruments), static_cast<uint16>(MAX_INSTRUMENTS - 1));
668 if(fileHeader.speed)
669 m_nDefaultSpeed = fileHeader.speed;
670 if(fileHeader.tempo)
671 m_nDefaultTempo = Clamp(TEMPO(fileHeader.tempo, 0), ModSpecs::xmEx.GetTempoMin(), ModSpecs::xmEx.GetTempoMax());
672
673 m_SongFlags.reset();
674 m_SongFlags.set(SONG_LINEARSLIDES, (fileHeader.flags & XMFileHeader::linearSlides) != 0);
675 m_SongFlags.set(SONG_EXFILTERRANGE, (fileHeader.flags & XMFileHeader::extendedFilterRange) != 0);
676 if(m_SongFlags[SONG_EXFILTERRANGE] && madeWith == (verFT2Generic | verNewModPlug))
677 {
678 madeWith = verFT2Clone | verNewModPlug | verConfirmed;
679 }
680
681 ReadOrderFromFile<uint8>(Order(), file, fileHeader.orders);
682 if(fileHeader.orders == 0 && !madeWith[verEmptyOrders])
683 {
684 // Fix lamb_-_dark_lighthouse.xm, which only contains one pattern and an empty order list
685 Order().assign(1, 0);
686 }
687 file.Seek(fileHeader.size + 60);
688
689 if(fileHeader.version >= 0x0104)
690 {
691 ReadXMPatterns(file, fileHeader, *this);
692 }
693
694 bool isOXM = false;
695
696 // In case of XM versions < 1.04, we need to memorize the sample flags for all samples, as they are not stored immediately after the sample headers.
697 std::vector<SampleIO> sampleFlags;
698 uint8 sampleReserved = 0;
699 int instrType = -1;
700 bool unsupportedSamples = false;
701
702 // Reading instruments
703 for(INSTRUMENTINDEX instr = 1; instr <= m_nInstruments; instr++)
704 {
705 // First, try to read instrument header length...
706 uint32 headerSize = file.ReadUint32LE();
707 if(headerSize == 0)
708 {
709 headerSize = sizeof(XMInstrumentHeader);
710 }
711
712 // Now, read the complete struct.
713 file.SkipBack(4);
714 XMInstrumentHeader instrHeader;
715 file.ReadStructPartial(instrHeader, headerSize);
716
717 // Time for some version detection stuff.
718 if(madeWith == verOldModPlug)
719 {
720 madeWith.set(verConfirmed);
721 if(instrHeader.size == 245)
722 {
723 // ModPlug Tracker Alpha
724 m_dwLastSavedWithVersion = MPT_V("1.00.00.A5");
725 madeWithTracker = U_("ModPlug Tracker 1.0 alpha");
726 } else if(instrHeader.size == 263)
727 {
728 // ModPlug Tracker Beta (Beta 1 still behaves like Alpha, but Beta 3.3 does it this way)
729 m_dwLastSavedWithVersion = MPT_V("1.00.00.B3");
730 madeWithTracker = U_("ModPlug Tracker 1.0 beta");
731 } else
732 {
733 // WTF?
734 madeWith = (verUnknown | verConfirmed);
735 }
736 } else if(instrHeader.numSamples == 0)
737 {
738 // Empty instruments make tracker identification pretty easy!
739 if(instrHeader.size == 263 && instrHeader.sampleHeaderSize == 0 && madeWith[verNewModPlug])
740 madeWith.set(verConfirmed);
741 else if(instrHeader.size != 29 && madeWith[verDigiTrakker])
742 madeWith.reset(verDigiTrakker);
743 else if(madeWith[verFT2Clone | verFT2Generic] && instrHeader.size != 33)
744 {
745 // Sure isn't FT2.
746 // Note: FT2 NORMALLY writes shdr=40 for all samples, but sometimes it
747 // just happens to write random garbage there instead. Surprise!
748 // Note: 4-mat's eternity.xm has an instrument header size of 29.
749 madeWith = verUnknown;
750 }
751 }
752
753 if(AllocateInstrument(instr) == nullptr)
754 {
755 continue;
756 }
757
758 instrHeader.ConvertToMPT(*Instruments[instr]);
759
760 if(instrType == -1)
761 {
762 instrType = instrHeader.type;
763 } else if(instrType != instrHeader.type && madeWith[verFT2Generic])
764 {
765 // FT2 writes some random junk for the instrument type field,
766 // but it's always the SAME junk for every instrument saved.
767 madeWith.reset(verFT2Generic);
768 madeWith.set(verFT2Clone);
769 }
770
771 if(instrHeader.numSamples > 0)
772 {
773 // Yep, there are some samples associated with this instrument.
774 if((instrHeader.instrument.midiEnabled | instrHeader.instrument.midiChannel | instrHeader.instrument.midiProgram | instrHeader.instrument.muteComputer) != 0)
775 {
776 // Definitely not an old MPT.
777 madeWith.reset(verOldModPlug | verNewModPlug);
778 }
779
780 // Read sample headers
781 std::vector<SAMPLEINDEX> sampleSlots = AllocateXMSamples(*this, instrHeader.numSamples);
782
783 // Update sample assignment map
784 for(size_t k = 0 + 12; k < 96 + 12; k++)
785 {
786 if(Instruments[instr]->Keyboard[k] < sampleSlots.size())
787 {
788 Instruments[instr]->Keyboard[k] = sampleSlots[Instruments[instr]->Keyboard[k]];
789 }
790 }
791
792 if(fileHeader.version >= 0x0104)
793 {
794 sampleFlags.clear();
795 }
796 // Need to memorize those if we're going to skip any samples...
797 std::vector<uint32> sampleSize(instrHeader.numSamples);
798
799 // Early versions of Sk@le Tracker set instrHeader.sampleHeaderSize = 0 (IFULOVE.XM)
800 // cybernostra weekend has instrHeader.sampleHeaderSize = 0x12, which would leave out the sample name, but FT2 still reads the name.
801 MPT_ASSERT(instrHeader.sampleHeaderSize == 0 || instrHeader.sampleHeaderSize == sizeof(XMSample));
802
803 for(SAMPLEINDEX sample = 0; sample < instrHeader.numSamples; sample++)
804 {
805 XMSample sampleHeader;
806 file.ReadStruct(sampleHeader);
807
808 sampleFlags.push_back(sampleHeader.GetSampleFormat());
809 sampleSize[sample] = sampleHeader.length;
810 sampleReserved |= sampleHeader.reserved;
811
812 if(sample < sampleSlots.size())
813 {
814 SAMPLEINDEX mptSample = sampleSlots[sample];
815
816 sampleHeader.ConvertToMPT(Samples[mptSample]);
817 instrHeader.instrument.ApplyAutoVibratoToMPT(Samples[mptSample]);
818
819 m_szNames[mptSample] = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name);
820
821 if((sampleHeader.flags & 3) == 3 && madeWith[verNewModPlug])
822 {
823 // MPT 1.09 and maybe newer / older versions set both loop flags for bidi loops.
824 madeWith.set(verModPlug1_09);
825 }
826 }
827 }
828
829 // Read samples
830 if(fileHeader.version >= 0x0104)
831 {
832 for(SAMPLEINDEX sample = 0; sample < instrHeader.numSamples; sample++)
833 {
834 // Sample 15 in dirtysex.xm by J/M/T/M is a 16-bit sample with an odd size of 0x18B according to the header, while the real sample size would be 0x18A.
835 // Always read as many bytes as specified in the header, even if the sample reader would probably read less bytes.
836 FileReader sampleChunk = file.ReadChunk(sampleFlags[sample].GetEncoding() != SampleIO::ADPCM ? sampleSize[sample] : (16 + (sampleSize[sample] + 1) / 2));
837 if(sample < sampleSlots.size() && (loadFlags & loadSampleData))
838 {
839 if(!ReadSampleData(Samples[sampleSlots[sample]], sampleFlags[sample], sampleChunk, isOXM))
840 {
841 unsupportedSamples = true;
842 }
843 }
844 }
845 }
846 }
847 }
848
849 if(sampleReserved == 0 && madeWith[verNewModPlug] && memchr(fileHeader.songName, '\0', sizeof(fileHeader.songName)) != nullptr)
850 {
851 // Null-terminated song name: Quite possibly MPT. (could really be an MPT-made file resaved in FT2, though)
852 madeWith.set(verConfirmed);
853 }
854
855 if(fileHeader.version < 0x0104)
856 {
857 // Load Patterns and Samples (Version 1.02 and 1.03)
858 if(loadFlags & (loadPatternData | loadSampleData))
859 {
860 ReadXMPatterns(file, fileHeader, *this);
861 }
862
863 if(loadFlags & loadSampleData)
864 {
865 for(SAMPLEINDEX sample = 1; sample <= GetNumSamples(); sample++)
866 {
867 sampleFlags[sample - 1].ReadSample(Samples[sample], file);
868 }
869 }
870 }
871
872 if(unsupportedSamples)
873 {
874 AddToLog(LogWarning, U_("Some compressed samples could not be loaded because they use an unsupported codec."));
875 }
876
877 // Read song comments: "text"
878 if(file.ReadMagic("text"))
879 {
880 m_songMessage.Read(file, file.ReadUint32LE(), SongMessage::leCR);
881 madeWith.set(verConfirmed);
882 }
883
884 // Read midi config: "MIDI"
885 bool hasMidiConfig = false;
886 if(file.ReadMagic("MIDI"))
887 {
888 file.ReadStructPartial<MIDIMacroConfigData>(m_MidiCfg, file.ReadUint32LE());
889 m_MidiCfg.Sanitize();
890 hasMidiConfig = true;
891 madeWith.set(verConfirmed);
892 }
893
894 // Read pattern names: "PNAM"
895 if(file.ReadMagic("PNAM"))
896 {
897 const PATTERNINDEX namedPats = std::min(static_cast<PATTERNINDEX>(file.ReadUint32LE() / MAX_PATTERNNAME), Patterns.Size());
898
899 for(PATTERNINDEX pat = 0; pat < namedPats; pat++)
900 {
901 char patName[MAX_PATTERNNAME];
902 file.ReadString<mpt::String::maybeNullTerminated>(patName, MAX_PATTERNNAME);
903 Patterns[pat].SetName(patName);
904 }
905 madeWith.set(verConfirmed);
906 }
907
908 // Read channel names: "CNAM"
909 if(file.ReadMagic("CNAM"))
910 {
911 const CHANNELINDEX namedChans = std::min(static_cast<CHANNELINDEX>(file.ReadUint32LE() / MAX_CHANNELNAME), GetNumChannels());
912 for(CHANNELINDEX chn = 0; chn < namedChans; chn++)
913 {
914 file.ReadString<mpt::String::maybeNullTerminated>(ChnSettings[chn].szName, MAX_CHANNELNAME);
915 }
916 madeWith.set(verConfirmed);
917 }
918
919 // Read mix plugins information
920 if(file.CanRead(8))
921 {
922 FileReader::off_t oldPos = file.GetPosition();
923 LoadMixPlugins(file);
924 if(file.GetPosition() != oldPos)
925 {
926 madeWith.set(verConfirmed);
927 }
928 }
929
930 if(madeWith[verConfirmed])
931 {
932 if(madeWith[verModPlug1_09])
933 {
934 m_dwLastSavedWithVersion = MPT_V("1.09.00.00");
935 madeWithTracker = U_("ModPlug Tracker 1.09");
936 } else if(madeWith[verNewModPlug])
937 {
938 m_dwLastSavedWithVersion = MPT_V("1.16.00.00");
939 madeWithTracker = U_("ModPlug Tracker 1.10 - 1.16");
940 }
941 }
942
943 if(!memcmp(fileHeader.trackerName, "OpenMPT ", 8))
944 {
945 // Hey, I know this tracker!
946 std::string mptVersion(fileHeader.trackerName + 8, 12);
947 m_dwLastSavedWithVersion = Version::Parse(mpt::ToUnicode(mpt::Charset::ASCII, mptVersion));
948 madeWith = verOpenMPT | verConfirmed;
949
950 if(m_dwLastSavedWithVersion < MPT_V("1.22.07.19"))
951 m_nMixLevels = MixLevels::Compatible;
952 else
953 m_nMixLevels = MixLevels::CompatibleFT2;
954 }
955
956 if(m_dwLastSavedWithVersion && !madeWith[verOpenMPT])
957 {
958 m_nMixLevels = MixLevels::Original;
959 m_playBehaviour.reset();
960 }
961
962 if(madeWith[verFT2Generic])
963 {
964 m_nMixLevels = MixLevels::CompatibleFT2;
965
966 if(!hasMidiConfig)
967 {
968 // FT2 allows typing in arbitrary unsupported effect letters such as Zxx.
969 // Prevent these commands from being interpreted as filter commands by erasing the default MIDI Config.
970 m_MidiCfg.ClearZxxMacros();
971 }
972
973 if(fileHeader.version >= 0x0104 // Old versions of FT2 didn't have (smooth) ramping. Disable it for those versions where we can be sure that there should be no ramping.
974 #ifdef MODPLUG_TRACKER
975 && TrackerSettings::Instance().autoApplySmoothFT2Ramping
976 #endif // MODPLUG_TRACKER
977 )
978 {
979 // apply FT2-style super-soft volume ramping
980 m_playBehaviour.set(kFT2VolumeRamping);
981 }
982 }
983
984 if(madeWithTracker.empty())
985 {
986 if(madeWith[verDigiTrakker] && sampleReserved == 0 && (instrType ? instrType : -1) == -1)
987 {
988 madeWithTracker = U_("DigiTrakker");
989 } else if(madeWith[verFT2Generic])
990 {
991 madeWithTracker = U_("FastTracker 2 or compatible");
992 } else
993 {
994 madeWithTracker = U_("Unknown");
995 }
996 }
997
998 bool isOpenMPTMade = false; // specific for OpenMPT 1.17+
999 if(GetNumInstruments())
1000 {
1001 isOpenMPTMade = LoadExtendedInstrumentProperties(file);
1002 }
1003
1004 LoadExtendedSongProperties(file, true, &isOpenMPTMade);
1005
1006 if(isOpenMPTMade && m_dwLastSavedWithVersion < MPT_V("1.17.00.00"))
1007 {
1008 // Up to OpenMPT 1.17.02.45 (r165), it was possible that the "last saved with" field was 0
1009 // when saving a file in OpenMPT for the first time.
1010 m_dwLastSavedWithVersion = MPT_V("1.17.00.00");
1011 }
1012
1013 if(m_dwLastSavedWithVersion >= MPT_V("1.17.00.00"))
1014 {
1015 madeWithTracker = U_("OpenMPT ") + m_dwLastSavedWithVersion.ToUString();
1016 }
1017
1018 // We no longer allow any --- or +++ items in the order list now.
1019 if(m_dwLastSavedWithVersion && m_dwLastSavedWithVersion < MPT_V("1.22.02.02"))
1020 {
1021 if(!Patterns.IsValidPat(0xFE))
1022 Order().RemovePattern(0xFE);
1023 if(!Patterns.IsValidPat(0xFF))
1024 Order().Replace(0xFF, Order.GetInvalidPatIndex());
1025 }
1026
1027 m_modFormat.formatName = MPT_UFORMAT("FastTracker 2 v{}.{}")(fileHeader.version >> 8, mpt::ufmt::hex0<2>(fileHeader.version & 0xFF));
1028 m_modFormat.madeWithTracker = std::move(madeWithTracker);
1029 m_modFormat.charset = (m_dwLastSavedWithVersion || isMadTracker) ? mpt::Charset::Windows1252 : mpt::Charset::CP437;
1030 if(isOXM)
1031 {
1032 m_modFormat.originalFormatName = std::move(m_modFormat.formatName);
1033 m_modFormat.formatName = U_("OggMod FastTracker 2");
1034 m_modFormat.type = U_("oxm");
1035 m_modFormat.originalType = U_("xm");
1036 } else
1037 {
1038 m_modFormat.type = U_("xm");
1039 }
1040
1041 return true;
1042 }
1043
1044
1045 #ifndef MODPLUG_NO_FILESAVE
1046
1047
SaveXM(std::ostream & f,bool compatibilityExport)1048 bool CSoundFile::SaveXM(std::ostream &f, bool compatibilityExport)
1049 {
1050
1051 bool addChannel = false; // avoid odd channel count for FT2 compatibility
1052
1053 XMFileHeader fileHeader;
1054 MemsetZero(fileHeader);
1055
1056 memcpy(fileHeader.signature, "Extended Module: ", 17);
1057 mpt::String::WriteBuf(mpt::String::spacePadded, fileHeader.songName) = m_songName;
1058 fileHeader.eof = 0x1A;
1059 const std::string openMptTrackerName = mpt::ToCharset(GetCharsetFile(), Version::Current().GetOpenMPTVersionString());
1060 mpt::String::WriteBuf(mpt::String::spacePadded, fileHeader.trackerName) = openMptTrackerName;
1061
1062 // Writing song header
1063 fileHeader.version = 0x0104; // XM Format v1.04
1064 fileHeader.size = sizeof(XMFileHeader) - 60; // minus everything before this field
1065 fileHeader.restartPos = Order().GetRestartPos();
1066
1067 fileHeader.channels = m_nChannels;
1068 if((m_nChannels % 2u) && m_nChannels < 32)
1069 {
1070 // Avoid odd channel count for FT2 compatibility
1071 fileHeader.channels++;
1072 addChannel = true;
1073 } else if(compatibilityExport && fileHeader.channels > 32)
1074 {
1075 fileHeader.channels = 32;
1076 }
1077
1078 // Find out number of orders and patterns used.
1079 // +++ and --- patterns are not taken into consideration as FastTracker does not support them.
1080
1081 const ORDERINDEX trimmedLength = Order().GetLengthTailTrimmed();
1082 std::vector<uint8> orderList(trimmedLength);
1083 const ORDERINDEX orderLimit = compatibilityExport ? 256 : uint16_max;
1084 ORDERINDEX numOrders = 0;
1085 PATTERNINDEX numPatterns = Patterns.GetNumPatterns();
1086 bool changeOrderList = false;
1087 for(ORDERINDEX ord = 0; ord < trimmedLength; ord++)
1088 {
1089 PATTERNINDEX pat = Order()[ord];
1090 if(pat == Order.GetIgnoreIndex() || pat == Order.GetInvalidPatIndex() || pat > uint8_max)
1091 {
1092 changeOrderList = true;
1093 } else if(numOrders < orderLimit)
1094 {
1095 orderList[numOrders++] = static_cast<uint8>(pat);
1096 if(pat >= numPatterns)
1097 numPatterns = pat + 1;
1098 }
1099 }
1100 if(changeOrderList)
1101 {
1102 AddToLog(LogWarning, U_("Skip and stop order list items (+++ and ---) are not saved in XM files."));
1103 }
1104 orderList.resize(compatibilityExport ? 256 : numOrders);
1105
1106 fileHeader.orders = numOrders;
1107 fileHeader.patterns = numPatterns;
1108 fileHeader.size += static_cast<uint32>(orderList.size());
1109
1110 uint16 writeInstruments;
1111 if(m_nInstruments > 0)
1112 fileHeader.instruments = writeInstruments = m_nInstruments;
1113 else
1114 fileHeader.instruments = writeInstruments = m_nSamples;
1115
1116 if(m_SongFlags[SONG_LINEARSLIDES]) fileHeader.flags |= XMFileHeader::linearSlides;
1117 if(m_SongFlags[SONG_EXFILTERRANGE] && !compatibilityExport) fileHeader.flags |= XMFileHeader::extendedFilterRange;
1118 fileHeader.flags = fileHeader.flags;
1119
1120 // Fasttracker 2 will happily accept any tempo faster than 255 BPM. XMPlay does also support this, great!
1121 fileHeader.tempo = mpt::saturate_cast<uint16>(m_nDefaultTempo.GetInt());
1122 fileHeader.speed = static_cast<uint16>(Clamp(m_nDefaultSpeed, 1u, 31u));
1123
1124 mpt::IO::Write(f, fileHeader);
1125
1126 // Write processed order list
1127 mpt::IO::Write(f, orderList);
1128
1129 // Writing patterns
1130
1131 #define ASSERT_CAN_WRITE(x) \
1132 if(len > s.size() - x) /*Buffer running out? Make it larger.*/ \
1133 s.resize(s.size() + 10 * 1024, 0);
1134 std::vector<uint8> s(64 * 64 * 5, 0);
1135
1136 for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
1137 {
1138 uint8 patHead[9] = { 0 };
1139 patHead[0] = 9;
1140
1141 if(!Patterns.IsValidPat(pat))
1142 {
1143 // There's nothing to write... chicken out.
1144 patHead[5] = 64;
1145 mpt::IO::Write(f, patHead);
1146 continue;
1147 }
1148
1149 const uint16 numRows = mpt::saturate_cast<uint16>(Patterns[pat].GetNumRows());
1150 patHead[5] = static_cast<uint8>(numRows & 0xFF);
1151 patHead[6] = static_cast<uint8>(numRows >> 8);
1152
1153 auto p = Patterns[pat].cbegin();
1154 size_t len = 0;
1155 // Empty patterns are always loaded as 64-row patterns in FT2, regardless of their real size...
1156 bool emptyPattern = true;
1157
1158 for(size_t j = m_nChannels * numRows; j > 0; j--, p++)
1159 {
1160 // Don't write more than 32 channels
1161 if(compatibilityExport && m_nChannels - ((j - 1) % m_nChannels) > 32) continue;
1162
1163 uint8 note = p->note;
1164 uint8 command = p->command, param = p->param;
1165 ModSaveCommand(command, param, true, compatibilityExport);
1166
1167 if (note >= NOTE_MIN_SPECIAL) note = 97; else
1168 if ((note <= 12) || (note > 96+12)) note = 0; else
1169 note -= 12;
1170 uint8 vol = 0;
1171 if (p->volcmd != VOLCMD_NONE)
1172 {
1173 switch(p->volcmd)
1174 {
1175 case VOLCMD_VOLUME: vol = 0x10 + p->vol; break;
1176 case VOLCMD_VOLSLIDEDOWN: vol = 0x60 + (p->vol & 0x0F); break;
1177 case VOLCMD_VOLSLIDEUP: vol = 0x70 + (p->vol & 0x0F); break;
1178 case VOLCMD_FINEVOLDOWN: vol = 0x80 + (p->vol & 0x0F); break;
1179 case VOLCMD_FINEVOLUP: vol = 0x90 + (p->vol & 0x0F); break;
1180 case VOLCMD_VIBRATOSPEED: vol = 0xA0 + (p->vol & 0x0F); break;
1181 case VOLCMD_VIBRATODEPTH: vol = 0xB0 + (p->vol & 0x0F); break;
1182 case VOLCMD_PANNING: vol = 0xC0 + (p->vol / 4); if (vol > 0xCF) vol = 0xCF; break;
1183 case VOLCMD_PANSLIDELEFT: vol = 0xD0 + (p->vol & 0x0F); break;
1184 case VOLCMD_PANSLIDERIGHT: vol = 0xE0 + (p->vol & 0x0F); break;
1185 case VOLCMD_TONEPORTAMENTO: vol = 0xF0 + (p->vol & 0x0F); break;
1186 }
1187 // Those values are ignored in FT2. Don't save them, also to avoid possible problems with other trackers (or MPT itself)
1188 if(compatibilityExport && p->vol == 0)
1189 {
1190 switch(p->volcmd)
1191 {
1192 case VOLCMD_VOLUME:
1193 case VOLCMD_PANNING:
1194 case VOLCMD_VIBRATODEPTH:
1195 case VOLCMD_TONEPORTAMENTO:
1196 case VOLCMD_PANSLIDELEFT: // Doesn't have memory, but does weird things with zero param.
1197 break;
1198 default:
1199 // no memory here.
1200 vol = 0;
1201 }
1202 }
1203 }
1204
1205 // no need to fix non-empty patterns
1206 if(!p->IsEmpty())
1207 emptyPattern = false;
1208
1209 // Apparently, completely empty patterns are loaded as empty 64-row patterns in FT2, regardless of their original size.
1210 // We have to avoid this, so we add a "break to row 0" command in the last row.
1211 if(j == 1 && emptyPattern && numRows != 64)
1212 {
1213 command = 0x0D;
1214 param = 0;
1215 }
1216
1217 if ((note) && (p->instr) && (vol > 0x0F) && (command) && (param))
1218 {
1219 s[len++] = note;
1220 s[len++] = p->instr;
1221 s[len++] = vol;
1222 s[len++] = command;
1223 s[len++] = param;
1224 } else
1225 {
1226 uint8 b = 0x80;
1227 if (note) b |= 0x01;
1228 if (p->instr) b |= 0x02;
1229 if (vol >= 0x10) b |= 0x04;
1230 if (command) b |= 0x08;
1231 if (param) b |= 0x10;
1232 s[len++] = b;
1233 if (b & 1) s[len++] = note;
1234 if (b & 2) s[len++] = p->instr;
1235 if (b & 4) s[len++] = vol;
1236 if (b & 8) s[len++] = command;
1237 if (b & 16) s[len++] = param;
1238 }
1239
1240 if(addChannel && (j % m_nChannels == 1 || m_nChannels == 1))
1241 {
1242 ASSERT_CAN_WRITE(1);
1243 s[len++] = 0x80;
1244 }
1245
1246 ASSERT_CAN_WRITE(5);
1247 }
1248
1249 if(emptyPattern && numRows == 64)
1250 {
1251 // Be smart when saving empty patterns!
1252 len = 0;
1253 }
1254
1255 // Reaching the limits of file format?
1256 if(len > uint16_max)
1257 {
1258 AddToLog(LogWarning, MPT_UFORMAT("Warning: File format limit was reached. Some pattern data may not get written to file. (pattern {})")(pat));
1259 len = uint16_max;
1260 }
1261
1262 patHead[7] = static_cast<uint8>(len & 0xFF);
1263 patHead[8] = static_cast<uint8>(len >> 8);
1264 mpt::IO::Write(f, patHead);
1265 if(len) mpt::IO::WriteRaw(f, s.data(), len);
1266 }
1267
1268 #undef ASSERT_CAN_WRITE
1269
1270 // Check which samples are referenced by which instruments (for assigning unreferenced samples to instruments)
1271 std::vector<bool> sampleAssigned(GetNumSamples() + 1, false);
1272 for(INSTRUMENTINDEX ins = 1; ins <= GetNumInstruments(); ins++)
1273 {
1274 if(Instruments[ins] != nullptr)
1275 {
1276 Instruments[ins]->GetSamples(sampleAssigned);
1277 }
1278 }
1279
1280 // Writing instruments
1281 for(INSTRUMENTINDEX ins = 1; ins <= writeInstruments; ins++)
1282 {
1283 XMInstrumentHeader insHeader;
1284 std::vector<SAMPLEINDEX> samples;
1285
1286 if(GetNumInstruments())
1287 {
1288 if(Instruments[ins] != nullptr)
1289 {
1290 // Convert instrument
1291 insHeader.ConvertToXM(*Instruments[ins], compatibilityExport);
1292
1293 samples = insHeader.instrument.GetSampleList(*Instruments[ins], compatibilityExport);
1294 if(samples.size() > 0 && samples[0] <= GetNumSamples())
1295 {
1296 // Copy over auto-vibrato settings of first sample
1297 insHeader.instrument.ApplyAutoVibratoToXM(Samples[samples[0]], GetType());
1298 }
1299
1300 std::vector<SAMPLEINDEX> additionalSamples;
1301
1302 // Try to save "instrument-less" samples as well by adding those after the "normal" samples of our sample.
1303 // We look for unassigned samples directly after the samples assigned to our current instrument, so if
1304 // e.g. sample 1 is assigned to instrument 1 and samples 2 to 10 aren't assigned to any instrument,
1305 // we will assign those to sample 1. Any samples before the first referenced sample are going to be lost,
1306 // but hey, I wrote this mostly for preserving instrument texts in existing modules, where we shouldn't encounter this situation...
1307 for(auto smp : samples)
1308 {
1309 while(++smp <= GetNumSamples()
1310 && !sampleAssigned[smp]
1311 && insHeader.numSamples < (compatibilityExport ? 16 : 32))
1312 {
1313 sampleAssigned[smp] = true; // Don't want to add this sample again.
1314 additionalSamples.push_back(smp);
1315 insHeader.numSamples++;
1316 }
1317 }
1318
1319 samples.insert(samples.end(), additionalSamples.begin(), additionalSamples.end());
1320 } else
1321 {
1322 MemsetZero(insHeader);
1323 }
1324 } else
1325 {
1326 // Convert samples to instruments
1327 MemsetZero(insHeader);
1328 insHeader.numSamples = 1;
1329 insHeader.instrument.ApplyAutoVibratoToXM(Samples[ins], GetType());
1330 samples.push_back(ins);
1331 }
1332
1333 insHeader.Finalise();
1334 size_t insHeaderSize = insHeader.size;
1335 mpt::IO::WritePartial(f, insHeader, insHeaderSize);
1336
1337 std::vector<SampleIO> sampleFlags(samples.size());
1338
1339 // Write Sample Headers
1340 for(SAMPLEINDEX smp = 0; smp < samples.size(); smp++)
1341 {
1342 XMSample xmSample;
1343 if(samples[smp] <= GetNumSamples())
1344 {
1345 xmSample.ConvertToXM(Samples[samples[smp]], GetType(), compatibilityExport);
1346 } else
1347 {
1348 MemsetZero(xmSample);
1349 }
1350 sampleFlags[smp] = xmSample.GetSampleFormat();
1351
1352 mpt::String::WriteBuf(mpt::String::spacePadded, xmSample.name) = m_szNames[samples[smp]];
1353
1354 mpt::IO::Write(f, xmSample);
1355 }
1356
1357 // Write Sample Data
1358 for(SAMPLEINDEX smp = 0; smp < samples.size(); smp++)
1359 {
1360 if(samples[smp] <= GetNumSamples())
1361 {
1362 sampleFlags[smp].WriteSample(f, Samples[samples[smp]]);
1363 }
1364 }
1365 }
1366
1367 if(!compatibilityExport)
1368 {
1369 // Writing song comments
1370 if(!m_songMessage.empty())
1371 {
1372 uint32 size = mpt::saturate_cast<uint32>(m_songMessage.length());
1373 mpt::IO::WriteRaw(f, "text", 4);
1374 mpt::IO::WriteIntLE<uint32>(f, size);
1375 mpt::IO::WriteRaw(f, m_songMessage.c_str(), size);
1376 }
1377 // Writing midi cfg
1378 if(!m_MidiCfg.IsMacroDefaultSetupUsed())
1379 {
1380 mpt::IO::WriteRaw(f, "MIDI", 4);
1381 mpt::IO::WriteIntLE<uint32>(f, sizeof(MIDIMacroConfigData));
1382 mpt::IO::Write(f, static_cast<MIDIMacroConfigData &>(m_MidiCfg));
1383 }
1384 // Writing Pattern Names
1385 const PATTERNINDEX numNamedPats = Patterns.GetNumNamedPatterns();
1386 if(numNamedPats > 0)
1387 {
1388 mpt::IO::WriteRaw(f, "PNAM", 4);
1389 mpt::IO::WriteIntLE<uint32>(f, numNamedPats * MAX_PATTERNNAME);
1390 for(PATTERNINDEX pat = 0; pat < numNamedPats; pat++)
1391 {
1392 char name[MAX_PATTERNNAME];
1393 mpt::String::WriteBuf(mpt::String::maybeNullTerminated, name) = Patterns[pat].GetName();
1394 mpt::IO::Write(f, name);
1395 }
1396 }
1397 // Writing Channel Names
1398 {
1399 CHANNELINDEX numNamedChannels = 0;
1400 for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
1401 {
1402 if (ChnSettings[chn].szName[0]) numNamedChannels = chn + 1;
1403 }
1404 // Do it!
1405 if(numNamedChannels)
1406 {
1407 mpt::IO::WriteRaw(f, "CNAM", 4);
1408 mpt::IO::WriteIntLE<uint32>(f, numNamedChannels * MAX_CHANNELNAME);
1409 for(CHANNELINDEX chn = 0; chn < numNamedChannels; chn++)
1410 {
1411 char name[MAX_CHANNELNAME];
1412 mpt::String::WriteBuf(mpt::String::maybeNullTerminated, name) = ChnSettings[chn].szName;
1413 mpt::IO::Write(f, name);
1414 }
1415 }
1416 }
1417
1418 //Save hacked-on extra info
1419 SaveMixPlugins(&f);
1420 if(GetNumInstruments())
1421 {
1422 SaveExtendedInstrumentProperties(writeInstruments, f);
1423 }
1424 SaveExtendedSongProperties(f);
1425 }
1426
1427 return true;
1428 }
1429
1430 #endif // MODPLUG_NO_FILESAVE
1431
1432
1433 OPENMPT_NAMESPACE_END
1434