1 /*
2  * Load_s3m.cpp
3  * ------------
4  * Purpose: S3M (ScreamTracker 3) module loader / saver
5  * Notes  : (currently none)
6  * Authors: OpenMPT Devs
7  * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
8  */
9 
10 
11 #include "stdafx.h"
12 #include "Loaders.h"
13 #include "S3MTools.h"
14 #include "ITTools.h"
15 #ifndef MODPLUG_NO_FILESAVE
16 #include "mpt/io/base.hpp"
17 #include "mpt/io/io.hpp"
18 #include "mpt/io/io_stdstream.hpp"
19 #include "../common/mptFileIO.h"
20 #ifdef MODPLUG_TRACKER
21 #include "../mptrack/TrackerSettings.h"
22 #endif // MODPLUG_TRACKER
23 #endif // MODPLUG_NO_FILESAVE
24 #include "../common/version.h"
25 
26 
27 OPENMPT_NAMESPACE_BEGIN
28 
29 
S3MConvert(ModCommand & m,bool fromIT)30 void CSoundFile::S3MConvert(ModCommand &m, bool fromIT)
31 {
32 	switch(m.command | 0x40)
33 	{
34 	case '@': m.command = (m.param ? CMD_DUMMY : CMD_NONE); break;
35 	case 'A': m.command = CMD_SPEED; break;
36 	case 'B': m.command = CMD_POSITIONJUMP; break;
37 	case 'C': m.command = CMD_PATTERNBREAK; if (!fromIT) m.param = (m.param >> 4) * 10 + (m.param & 0x0F); break;
38 	case 'D': m.command = CMD_VOLUMESLIDE; break;
39 	case 'E': m.command = CMD_PORTAMENTODOWN; break;
40 	case 'F': m.command = CMD_PORTAMENTOUP; break;
41 	case 'G': m.command = CMD_TONEPORTAMENTO; break;
42 	case 'H': m.command = CMD_VIBRATO; break;
43 	case 'I': m.command = CMD_TREMOR; break;
44 	case 'J': m.command = CMD_ARPEGGIO; break;
45 	case 'K': m.command = CMD_VIBRATOVOL; break;
46 	case 'L': m.command = CMD_TONEPORTAVOL; break;
47 	case 'M': m.command = CMD_CHANNELVOLUME; break;
48 	case 'N': m.command = CMD_CHANNELVOLSLIDE; break;
49 	case 'O': m.command = CMD_OFFSET; break;
50 	case 'P': m.command = CMD_PANNINGSLIDE; break;
51 	case 'Q': m.command = CMD_RETRIG; break;
52 	case 'R': m.command = CMD_TREMOLO; break;
53 	case 'S': m.command = CMD_S3MCMDEX; break;
54 	case 'T': m.command = CMD_TEMPO; break;
55 	case 'U': m.command = CMD_FINEVIBRATO; break;
56 	case 'V': m.command = CMD_GLOBALVOLUME; break;
57 	case 'W': m.command = CMD_GLOBALVOLSLIDE; break;
58 	case 'X': m.command = CMD_PANNING8; break;
59 	case 'Y': m.command = CMD_PANBRELLO; break;
60 	case 'Z': m.command = CMD_MIDI; break;
61 	case '\\': m.command = fromIT ? CMD_SMOOTHMIDI : CMD_MIDI; break;
62 	// Chars under 0x40 don't save properly, so the following commands don't map to their pattern editor representations
63 	case ']': m.command = fromIT ? CMD_DELAYCUT : CMD_NONE; break;
64 	case '[': m.command = fromIT ? CMD_XPARAM : CMD_NONE; break;
65 	case '^': m.command = fromIT ? CMD_FINETUNE : CMD_NONE; break;
66 	case '_': m.command = fromIT ? CMD_FINETUNE_SMOOTH : CMD_NONE; break;
67 	// BeRoTracker extensions
68 	case '1' + 0x41: m.command = fromIT ? CMD_KEYOFF : CMD_NONE; break;
69 	case '2' + 0x41: m.command = fromIT ? CMD_SETENVPOSITION : CMD_NONE; break;
70 	default: m.command = CMD_NONE;
71 	}
72 }
73 
74 #ifndef MODPLUG_NO_FILESAVE
75 
S3MSaveConvert(uint8 & command,uint8 & param,bool toIT,bool compatibilityExport) const76 void CSoundFile::S3MSaveConvert(uint8 &command, uint8 &param, bool toIT, bool compatibilityExport) const
77 {
78 	const bool extendedIT = !compatibilityExport && toIT;
79 	switch(command)
80 	{
81 	case CMD_DUMMY:           command = (param ? '@' : 0); break;
82 	case CMD_SPEED:           command = 'A'; break;
83 	case CMD_POSITIONJUMP:    command = 'B'; break;
84 	case CMD_PATTERNBREAK:    command = 'C'; if(!toIT) param = ((param / 10) << 4) + (param % 10); break;
85 	case CMD_VOLUMESLIDE:     command = 'D'; break;
86 	case CMD_PORTAMENTODOWN:  command = 'E'; if (param >= 0xE0 && (GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM))) param = 0xDF; break;
87 	case CMD_PORTAMENTOUP:    command = 'F'; if (param >= 0xE0 && (GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM))) param = 0xDF; break;
88 	case CMD_TONEPORTAMENTO:  command = 'G'; break;
89 	case CMD_VIBRATO:         command = 'H'; break;
90 	case CMD_TREMOR:          command = 'I'; break;
91 	case CMD_ARPEGGIO:        command = 'J'; break;
92 	case CMD_VIBRATOVOL:      command = 'K'; break;
93 	case CMD_TONEPORTAVOL:    command = 'L'; break;
94 	case CMD_CHANNELVOLUME:   command = 'M'; break;
95 	case CMD_CHANNELVOLSLIDE: command = 'N'; break;
96 	case CMD_OFFSETPERCENTAGE:
97 	case CMD_OFFSET:          command = 'O'; break;
98 	case CMD_PANNINGSLIDE:    command = 'P'; break;
99 	case CMD_RETRIG:          command = 'Q'; break;
100 	case CMD_TREMOLO:         command = 'R'; break;
101 	case CMD_S3MCMDEX:        command = 'S'; break;
102 	case CMD_TEMPO:           command = 'T'; break;
103 	case CMD_FINEVIBRATO:     command = 'U'; break;
104 	case CMD_GLOBALVOLUME:    command = 'V'; break;
105 	case CMD_GLOBALVOLSLIDE:  command = 'W'; break;
106 	case CMD_PANNING8:
107 		command = 'X';
108 		if(toIT && !(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_XM | MOD_TYPE_MOD)))
109 		{
110 			if (param == 0xA4) { command = 'S'; param = 0x91; }
111 			else if (param == 0x80) { param = 0xFF; }
112 			else if (param < 0x80) { param <<= 1; }
113 			else command = 0;
114 		} else if (!toIT && (GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_XM | MOD_TYPE_MOD)))
115 		{
116 			param >>= 1;
117 		}
118 		break;
119 	case CMD_PANBRELLO: command = 'Y'; break;
120 	case CMD_MIDI:      command = 'Z'; break;
121 	case CMD_SMOOTHMIDI:
122 		if(extendedIT)
123 			command = '\\';
124 		else
125 			command = 'Z';
126 		break;
127 	case CMD_XFINEPORTAUPDOWN:
128 		switch(param & 0xF0)
129 		{
130 		case 0x10: command = 'F'; param = (param & 0x0F) | 0xE0; break;
131 		case 0x20: command = 'E'; param = (param & 0x0F) | 0xE0; break;
132 		case 0x90: command = 'S'; break;
133 		default:   command = 0;
134 		}
135 		break;
136 	case CMD_MODCMDEX:
137 		{
138 			ModCommand m;
139 			m.command = CMD_MODCMDEX;
140 			m.param = param;
141 			m.ExtendedMODtoS3MEffect();
142 			command = m.command;
143 			param = m.param;
144 			S3MSaveConvert(command, param, toIT, compatibilityExport);
145 		}
146 		return;
147 	// Chars under 0x40 don't save properly, so map : to ] and # to [.
148 	case CMD_DELAYCUT:
149 		command = extendedIT ? ']' : 0;
150 		break;
151 	case CMD_XPARAM:
152 		command = extendedIT ? '[' : 0;
153 		break;
154 	case CMD_FINETUNE:
155 		command = extendedIT ? '^' : 0;
156 		break;
157 	case CMD_FINETUNE_SMOOTH:
158 		command = extendedIT ? '_' : 0;
159 		break;
160 	default:
161 		command = 0;
162 	}
163 	if(command == 0)
164 	{
165 		param = 0;
166 	}
167 
168 	command &= ~0x40;
169 }
170 
171 #endif // MODPLUG_NO_FILESAVE
172 
ValidateHeader(const S3MFileHeader & fileHeader)173 static bool ValidateHeader(const S3MFileHeader &fileHeader)
174 {
175 	if(std::memcmp(fileHeader.magic, "SCRM", 4)
176 		|| fileHeader.fileType != S3MFileHeader::idS3MType
177 		|| (fileHeader.formatVersion != S3MFileHeader::oldVersion && fileHeader.formatVersion != S3MFileHeader::newVersion)
178 		)
179 	{
180 		return false;
181 	}
182 	return true;
183 }
184 
185 
GetHeaderMinimumAdditionalSize(const S3MFileHeader & fileHeader)186 static uint64 GetHeaderMinimumAdditionalSize(const S3MFileHeader &fileHeader)
187 {
188 	return fileHeader.ordNum + (fileHeader.smpNum + fileHeader.patNum) * 2;
189 }
190 
191 
ProbeFileHeaderS3M(MemoryFileReader file,const uint64 * pfilesize)192 CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderS3M(MemoryFileReader file, const uint64 *pfilesize)
193 {
194 	S3MFileHeader fileHeader;
195 	if(!file.ReadStruct(fileHeader))
196 	{
197 		return ProbeWantMoreData;
198 	}
199 	if(!ValidateHeader(fileHeader))
200 	{
201 		return ProbeFailure;
202 	}
203 	return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
204 }
205 
206 
ReadS3M(FileReader & file,ModLoadingFlags loadFlags)207 bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
208 {
209 	file.Rewind();
210 
211 	// Is it a valid S3M file?
212 	S3MFileHeader fileHeader;
213 	if(!file.ReadStruct(fileHeader))
214 	{
215 		return false;
216 	}
217 	if(!ValidateHeader(fileHeader))
218 	{
219 		return false;
220 	}
221 	if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
222 	{
223 		return false;
224 	}
225 	if(loadFlags == onlyVerifyHeader)
226 	{
227 		return true;
228 	}
229 
230 	InitializeGlobals(MOD_TYPE_S3M);
231 	m_nMinPeriod = 64;
232 	m_nMaxPeriod = 32767;
233 
234 	// ST3 ignored Zxx commands, so if we find that a file was made with ST3, we should erase all MIDI macros.
235 	bool keepMidiMacros = false;
236 
237 	mpt::ustring madeWithTracker;
238 	bool formatTrackerStr = false;
239 	bool nonCompatTracker = false;
240 	bool isST3 = false;
241 	bool isSchism = false;
242 	const int32 schismDateVersion = SchismTrackerEpoch + ((fileHeader.cwtv == 0x4FFF) ? fileHeader.reserved2 : (fileHeader.cwtv - 0x4050));
243 	switch(fileHeader.cwtv & S3MFileHeader::trackerMask)
244 	{
245 	case S3MFileHeader::trkAkord & S3MFileHeader::trackerMask:
246 		if(fileHeader.cwtv == S3MFileHeader::trkAkord)
247 			madeWithTracker = U_("Akord");
248 		break;
249 	case S3MFileHeader::trkScreamTracker:
250 		if(fileHeader.cwtv == S3MFileHeader::trkST3_20 && fileHeader.special == 0 && (fileHeader.ordNum & 0x0F) == 0 && fileHeader.ultraClicks == 0 && (fileHeader.flags & ~0x50) == 0)
251 		{
252 			// MPT and OpenMPT before 1.17.03.02 - Simply keep default (filter) MIDI macros
253 			if((fileHeader.masterVolume & 0x80) != 0)
254 			{
255 				m_dwLastSavedWithVersion = MPT_V("1.16.00.00");
256 				madeWithTracker = U_("ModPlug Tracker / OpenMPT 1.17");
257 			} else
258 			{
259 				// MPT 1.0 alpha5 doesn't set the stereo flag, but MPT 1.0 beta1 does.
260 				m_dwLastSavedWithVersion = MPT_V("1.00.00.00");
261 				madeWithTracker = U_("ModPlug Tracker 1.0 alpha");
262 			}
263 			keepMidiMacros = true;
264 			nonCompatTracker = true;
265 			m_playBehaviour.set(kST3LimitPeriod);
266 		} else if(fileHeader.cwtv == S3MFileHeader::trkST3_20 && fileHeader.special == 0 && fileHeader.ultraClicks == 0 && fileHeader.flags == 0 && fileHeader.usePanningTable == 0)
267 		{
268 			madeWithTracker = U_("Velvet Studio");
269 		} else
270 		{
271 			// ST3.20 should only ever write ultra-click values 16, 24 and 32 (corresponding to 8, 12 and 16 in the GUI), ST3.01/3.03 should only write 0,
272 			// though several ST3.01/3.03 files with ultra-click values of 16 have been found as well.
273 			// However, we won't fingerprint these values here as it's unlikely that there is any other tracker out there disguising as ST3 and using a strange ultra-click value.
274 			// Also, re-saving a file with a strange ultra-click value in ST3 doesn't fix this value unless the user manually changes it, or if it's below 16.
275 			madeWithTracker = U_("Scream Tracker");
276 			formatTrackerStr = true;
277 			isST3 = true;
278 		}
279 		break;
280 	case S3MFileHeader::trkImagoOrpheus:
281 		madeWithTracker = U_("Imago Orpheus");
282 		formatTrackerStr = true;
283 		nonCompatTracker = true;
284 		break;
285 	case S3MFileHeader::trkImpulseTracker:
286 		if(fileHeader.cwtv <= S3MFileHeader::trkIT2_14)
287 		{
288 			madeWithTracker = U_("Impulse Tracker");
289 			formatTrackerStr = true;
290 		} else
291 		{
292 			madeWithTracker = MPT_UFORMAT("Impulse Tracker 2.14p{}")(fileHeader.cwtv - S3MFileHeader::trkIT2_14);
293 		}
294 		if(fileHeader.cwtv >= S3MFileHeader::trkIT2_07 && fileHeader.reserved3 != 0)
295 		{
296 			// Starting from version 2.07, IT stores the total edit time of a module in the "reserved" field
297 			uint32 editTime = DecodeITEditTimer(fileHeader.cwtv, fileHeader.reserved3);
298 
299 			FileHistory hist;
300 			hist.openTime = static_cast<uint32>(editTime * (HISTORY_TIMER_PRECISION / 18.2));
301 			m_FileHistory.push_back(hist);
302 		}
303 		nonCompatTracker = true;
304 		m_playBehaviour.set(kPeriodsAreHertz);
305 		m_playBehaviour.set(kITRetrigger);
306 		m_playBehaviour.set(kITShortSampleRetrig);
307 		m_playBehaviour.set(kST3SampleSwap);  // Not exactly like ST3, but close enough
308 		m_nMinPeriod = 1;
309 		break;
310 	case S3MFileHeader::trkSchismTracker:
311 		if(fileHeader.cwtv == S3MFileHeader::trkBeRoTrackerOld)
312 		{
313 			madeWithTracker = U_("BeRoTracker");
314 			m_playBehaviour.set(kST3LimitPeriod);
315 		} else
316 		{
317 			madeWithTracker = GetSchismTrackerVersion(fileHeader.cwtv, fileHeader.reserved2);
318 			m_nMinPeriod = 1;
319 			isSchism = true;
320 			if(schismDateVersion >= SchismVersionFromDate<2021, 05, 02>::date)
321 				m_playBehaviour.set(kPeriodsAreHertz);
322 			if(schismDateVersion >= SchismVersionFromDate<2016, 05, 13>::date)
323 				m_playBehaviour.set(kITShortSampleRetrig);
324 		}
325 		nonCompatTracker = true;
326 		break;
327 	case S3MFileHeader::trkOpenMPT:
328 		{
329 			uint32 mptVersion = (fileHeader.cwtv & S3MFileHeader::versionMask) << 16;
330 			if(mptVersion >= 0x01'29'00'00)
331 				mptVersion |= fileHeader.reserved2;
332 			m_dwLastSavedWithVersion = Version(mptVersion);
333 			madeWithTracker = U_("OpenMPT ") + mpt::ufmt::val(m_dwLastSavedWithVersion);
334 		}
335 		break;
336 	case S3MFileHeader::trkBeRoTracker:
337 		madeWithTracker = U_("BeRoTracker");
338 		m_playBehaviour.set(kST3LimitPeriod);
339 		break;
340 	case S3MFileHeader::trkCreamTracker:
341 		madeWithTracker = U_("CreamTracker");
342 		break;
343 	default:
344 		if(fileHeader.cwtv == S3MFileHeader::trkCamoto)
345 			madeWithTracker = U_("Camoto");
346 		break;
347 	}
348 	if(formatTrackerStr)
349 	{
350 		madeWithTracker = MPT_UFORMAT("{} {}.{}")(madeWithTracker, (fileHeader.cwtv & 0xF00) >> 8, mpt::ufmt::hex0<2>(fileHeader.cwtv & 0xFF));
351 	}
352 
353 	m_modFormat.formatName = U_("Scream Tracker 3");
354 	m_modFormat.type = U_("s3m");
355 	m_modFormat.madeWithTracker = std::move(madeWithTracker);
356 	m_modFormat.charset = m_dwLastSavedWithVersion ? mpt::Charset::Windows1252 : mpt::Charset::CP437;
357 
358 	if(nonCompatTracker)
359 	{
360 		m_playBehaviour.reset(kST3NoMutedChannels);
361 		m_playBehaviour.reset(kST3EffectMemory);
362 		m_playBehaviour.reset(kST3PortaSampleChange);
363 		m_playBehaviour.reset(kST3VibratoMemory);
364 		m_playBehaviour.reset(KST3PortaAfterArpeggio);
365 		m_playBehaviour.reset(kST3OffsetWithoutInstrument);
366 		m_playBehaviour.reset(kApplyUpperPeriodLimit);
367 	}
368 
369 	if((fileHeader.cwtv & S3MFileHeader::trackerMask) > S3MFileHeader::trkScreamTracker)
370 	{
371 		if((fileHeader.cwtv & S3MFileHeader::trackerMask) != S3MFileHeader::trkImpulseTracker || fileHeader.cwtv >= S3MFileHeader::trkIT2_14)
372 		{
373 			// Keep MIDI macros if this is not an old IT version (BABYLON.S3M by Necros has Zxx commands and was saved with IT 2.05)
374 			keepMidiMacros = true;
375 		}
376 	}
377 
378 	m_MidiCfg.Reset();
379 	if(!keepMidiMacros)
380 	{
381 		// Remove macros so they don't interfere with tunes made in trackers that don't support Zxx
382 		m_MidiCfg.ClearZxxMacros();
383 	}
384 
385 	m_songName = mpt::String::ReadBuf(mpt::String::nullTerminated, fileHeader.name);
386 
387 	if(fileHeader.flags & S3MFileHeader::amigaLimits) m_SongFlags.set(SONG_AMIGALIMITS);
388 	if(fileHeader.flags & S3MFileHeader::st2Vibrato) m_SongFlags.set(SONG_S3MOLDVIBRATO);
389 
390 	if(fileHeader.cwtv == S3MFileHeader::trkST3_00 || (fileHeader.flags & S3MFileHeader::fastVolumeSlides) != 0)
391 	{
392 		m_SongFlags.set(SONG_FASTVOLSLIDES);
393 	}
394 
395 	// Speed
396 	m_nDefaultSpeed = fileHeader.speed;
397 	if(m_nDefaultSpeed == 0 || (m_nDefaultSpeed == 255 && isST3))
398 	{
399 		// Even though ST3 accepts the command AFF as expected, it mysteriously fails to load a default speed of 255...
400 		m_nDefaultSpeed = 6;
401 	}
402 
403 	// Tempo
404 	m_nDefaultTempo.Set(fileHeader.tempo);
405 	if(fileHeader.tempo < 33)
406 	{
407 		// ST3 also fails to load an otherwise valid default tempo of 32...
408 		m_nDefaultTempo.Set(isST3 ? 125 : 32);
409 	}
410 
411 	// Global Volume
412 	m_nDefaultGlobalVolume = std::min(fileHeader.globalVol.get(), uint8(64)) * 4u;
413 	// The following check is probably not very reliable, but it fixes a few tunes, e.g.
414 	// DARKNESS.S3M by Purple Motion (ST 3.00) and "Image of Variance" by C.C.Catch (ST 3.01):
415 	if(m_nDefaultGlobalVolume == 0 && fileHeader.cwtv < S3MFileHeader::trkST3_20)
416 	{
417 		m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME;
418 	}
419 
420 	if(fileHeader.formatVersion == S3MFileHeader::oldVersion && fileHeader.masterVolume < 8)
421 		m_nSamplePreAmp = std::min((fileHeader.masterVolume + 1) * 0x10, 0x7F);
422 	// These changes were probably only supposed to be done for older format revisions, where supposedly 0x10 was the stereo flag.
423 	// However, this version check is missing in ST3, so any mono file with a master volume of 18 will be converted to a stereo file with master volume 32.
424 	else if(fileHeader.masterVolume == 2 || fileHeader.masterVolume == (2 | 0x10))
425 		m_nSamplePreAmp = 0x20;
426 	else if(!(fileHeader.masterVolume & 0x7F))
427 		m_nSamplePreAmp = 48;
428 	else
429 		m_nSamplePreAmp = std::max(fileHeader.masterVolume & 0x7F, 0x10);  // Bit 7 = Stereo (we always use stereo)
430 
431 	const bool isStereo = (fileHeader.masterVolume & 0x80) != 0 || m_dwLastSavedWithVersion;
432 	if(!isStereo)
433 		m_nSamplePreAmp = Util::muldivr_unsigned(m_nSamplePreAmp, 8, 11);
434 
435 	// Approximately as loud as in DOSBox and a real SoundBlaster 16
436 	m_nVSTiVolume = 36;
437 	if(isSchism && schismDateVersion < SchismVersionFromDate<2018, 11, 12>::date)
438 		m_nVSTiVolume = 64;
439 
440 	// Channel setup
441 	m_nChannels = 4;
442 	std::bitset<32> isAdlibChannel;
443 	for(CHANNELINDEX i = 0; i < 32; i++)
444 	{
445 		ChnSettings[i].Reset();
446 
447 		uint8 ctype = fileHeader.channels[i] & ~0x80;
448 		if(fileHeader.channels[i] != 0xFF)
449 		{
450 			m_nChannels = i + 1;
451 			if(isStereo)
452 				ChnSettings[i].nPan = (ctype & 8) ? 0xCC : 0x33;	// 200 : 56
453 		}
454 		if(fileHeader.channels[i] & 0x80)
455 		{
456 			ChnSettings[i].dwFlags = CHN_MUTE;
457 		}
458 		if(ctype >= 16 && ctype <= 29)
459 		{
460 			// Adlib channel - except for OpenMPT 1.19 and older, which would write wrong channel types for PCM channels 16-32.
461 			// However, MPT/OpenMPT always wrote the extra panning table, so there is no need to consider this here.
462 			ChnSettings[i].nPan = 128;
463 			isAdlibChannel[i] = true;
464 		}
465 	}
466 	if(m_nChannels < 1)
467 	{
468 		m_nChannels = 1;
469 	}
470 
471 	ReadOrderFromFile<uint8>(Order(), file, fileHeader.ordNum, 0xFF, 0xFE);
472 
473 	// Read sample header offsets
474 	std::vector<uint16le> sampleOffsets;
475 	file.ReadVector(sampleOffsets, fileHeader.smpNum);
476 	// Read pattern offsets
477 	std::vector<uint16le> patternOffsets;
478 	file.ReadVector(patternOffsets, fileHeader.patNum);
479 
480 	// Read extended channel panning
481 	if(fileHeader.usePanningTable == S3MFileHeader::idPanning)
482 	{
483 		uint8 pan[32];
484 		file.ReadArray(pan);
485 		for(CHANNELINDEX i = 0; i < 32; i++)
486 		{
487 			if((pan[i] & 0x20) != 0 && (!isST3 || !isAdlibChannel[i]))
488 			{
489 				ChnSettings[i].nPan = (static_cast<uint16>(pan[i] & 0x0F) * 256 + 8) / 15;
490 			}
491 		}
492 	}
493 
494 	// Reading sample headers
495 	m_nSamples = std::min(static_cast<SAMPLEINDEX>(fileHeader.smpNum), static_cast<SAMPLEINDEX>(MAX_SAMPLES - 1));
496 	bool anySamples = false;
497 	uint16 gusAddresses = 0;
498 	for(SAMPLEINDEX smp = 0; smp < m_nSamples; smp++)
499 	{
500 		S3MSampleHeader sampleHeader;
501 
502 		if(!file.Seek(sampleOffsets[smp] * 16) || !file.ReadStruct(sampleHeader))
503 		{
504 			continue;
505 		}
506 
507 		sampleHeader.ConvertToMPT(Samples[smp + 1], isST3);
508 		m_szNames[smp + 1] = mpt::String::ReadBuf(mpt::String::nullTerminated, sampleHeader.name);
509 
510 		if(sampleHeader.sampleType < S3MSampleHeader::typeAdMel)
511 		{
512 			const uint32 sampleOffset = sampleHeader.GetSampleOffset();
513 			if((loadFlags & loadSampleData) && sampleHeader.length != 0 && file.Seek(sampleOffset))
514 			{
515 				sampleHeader.GetSampleFormat((fileHeader.formatVersion == S3MFileHeader::oldVersion)).ReadSample(Samples[smp + 1], file);
516 				anySamples = true;
517 			}
518 			gusAddresses |= sampleHeader.gusAddress;
519 		}
520 	}
521 
522 	if(isST3 && anySamples && !gusAddresses && fileHeader.cwtv != S3MFileHeader::trkST3_00)
523 	{
524 		// All Scream Tracker versions except for some probably early revisions of Scream Tracker 3.00 write GUS addresses. GUS support might not have existed at that point (1992).
525 		// Hence if a file claims to be written with ST3 (but not ST3.00), but has no GUS addresses, we deduce that it must be written by some other software (e.g. some PSM -> S3M conversions)
526 		isST3 = false;
527 		m_modFormat.madeWithTracker = U_("Unknown");
528 	} else if(isST3)
529 	{
530 		// Saving an S3M file in ST3 with the Gravis Ultrasound driver loaded will write a unique GUS memory address for each non-empty sample slot (and 0 for unused slots).
531 		// Re-saving that file in ST3 with the SoundBlaster driver loaded will reset the GUS address for all samples to 0 (unused) or 1 (used).
532 		// The first used sample will also have an address of 1 with the GUS driver.
533 		// So this is a safe way of telling if the file was last saved with the GUS driver loaded or not if there's more than one sample.
534 		const bool useGUS = gusAddresses > 1;
535 		m_playBehaviour.set(kST3PortaSampleChange, useGUS);
536 		m_playBehaviour.set(kST3SampleSwap, !useGUS);
537 		m_modFormat.madeWithTracker += useGUS ? UL_(" (GUS)") : UL_(" (SB)");
538 		// ST3's GUS driver doesn't use this value. Ignoring it fixes the balance between FM and PCM samples (e.g. in Rotagilla by Manwe)
539 		if(useGUS)
540 			m_nSamplePreAmp = 48;
541 	}
542 
543 	// Try to find out if Zxx commands are supposed to be panning commands (PixPlay).
544 	// Actually I am only aware of one module that uses this panning style, namely "Crawling Despair" by $volkraq
545 	// and I have no idea what PixPlay is, so this code is solely based on the sample text of that module.
546 	// We won't convert if there are not enough Zxx commands, too "high" Zxx commands
547 	// or there are only "left" or "right" pannings (we assume that stereo should be somewhat balanced),
548 	// and modules not made with an old version of ST3 were probably made in a tracker that supports panning anyway.
549 	bool pixPlayPanning = (fileHeader.cwtv < S3MFileHeader::trkST3_20);
550 	int zxxCountRight = 0, zxxCountLeft = 0;
551 
552 	// Reading patterns
553 	if(!(loadFlags & loadPatternData))
554 	{
555 		return true;
556 	}
557 	// Order list cannot contain pattern indices > 255, so do not even try to load higher patterns
558 	const PATTERNINDEX readPatterns = std::min(static_cast<PATTERNINDEX>(fileHeader.patNum), static_cast<PATTERNINDEX>(uint8_max));
559 	Patterns.ResizeArray(readPatterns);
560 	for(PATTERNINDEX pat = 0; pat < readPatterns; pat++)
561 	{
562 		// A zero parapointer indicates an empty pattern.
563 		if(!Patterns.Insert(pat, 64) || patternOffsets[pat] == 0 || !file.Seek(patternOffsets[pat] * 16))
564 		{
565 			continue;
566 		}
567 
568 		// Skip pattern length indication.
569 		// Some modules, for example http://aminet.net/mods/8voic/s3m_hunt.lha seem to have a wrong pattern length -
570 		// If you strictly adhere the pattern length, you won't read some patterns (e.g. 17) correctly in that module.
571 		// It's most likely a broken copy because there are other versions of the track which don't have this issue.
572 		// Still, we don't really need this information, so we just ignore it.
573 		file.Skip(2);
574 
575 		// Read pattern data
576 		ROWINDEX row = 0;
577 		PatternRow rowBase = Patterns[pat].GetRow(0);
578 
579 		while(row < 64)
580 		{
581 			uint8 info = file.ReadUint8();
582 
583 			if(info == s3mEndOfRow)
584 			{
585 				// End of row
586 				if(++row < 64)
587 				{
588 					rowBase = Patterns[pat].GetRow(row);
589 				}
590 				continue;
591 			}
592 
593 			CHANNELINDEX channel = (info & s3mChannelMask);
594 			ModCommand dummy;
595 			ModCommand &m = (channel < GetNumChannels()) ? rowBase[channel] : dummy;
596 
597 			if(info & s3mNotePresent)
598 			{
599 				const auto [note, instr] = file.ReadArray<uint8, 2>();
600 				if(note < 0xF0)
601 					m.note = static_cast<ModCommand::NOTE>(Clamp((note & 0x0F) + 12 * (note >> 4) + 12 + NOTE_MIN, NOTE_MIN, NOTE_MAX));
602 				else if(note == s3mNoteOff)
603 					m.note = NOTE_NOTECUT;
604 				else if(note == s3mNoteNone)
605 					m.note = NOTE_NONE;
606 				m.instr = instr;
607 			}
608 
609 			if(info & s3mVolumePresent)
610 			{
611 				uint8 volume = file.ReadUint8();
612 				if(volume >= 128 && volume <= 192)
613 				{
614 					m.volcmd = VOLCMD_PANNING;
615 					m.vol = volume - 128;
616 				} else
617 				{
618 					m.volcmd = VOLCMD_VOLUME;
619 					m.vol = std::min(volume, uint8(64));
620 				}
621 			}
622 
623 			if(info & s3mEffectPresent)
624 			{
625 				const auto [command, param] = file.ReadArray<uint8, 2>();
626 				m.command = command;
627 				m.param = param;
628 				S3MConvert(m, false);
629 
630 				if(m.command == CMD_S3MCMDEX && (m.param & 0xF0) == 0xA0 && fileHeader.cwtv < S3MFileHeader::trkST3_20)
631 				{
632 					// Convert old SAx panning to S8x (should only be found in PANIC.S3M by Purple Motion)
633 					m.param = 0x80 | ((m.param & 0x0F) ^ 8);
634 				} else if(m.command == CMD_MIDI)
635 				{
636 					// PixPlay panning test
637 					if(m.param > 0x0F)
638 					{
639 						// PixPlay has Z00 to Z0F panning, so we ignore this.
640 						pixPlayPanning = false;
641 					} else
642 					{
643 						if(m.param < 0x08)
644 							zxxCountLeft++;
645 						else if(m.param > 0x08)
646 							zxxCountRight++;
647 					}
648 				} else if(m.command == CMD_OFFSET && m.param == 0 && fileHeader.cwtv <= S3MFileHeader::trkST3_01)
649 				{
650 					// Offset command didn't have effect memory in ST3.01; fixed in ST3.03
651 					m.command = CMD_DUMMY;
652 				}
653 			}
654 		}
655 	}
656 
657 	if(pixPlayPanning && zxxCountLeft + zxxCountRight >= m_nChannels && (-zxxCountLeft + zxxCountRight) < static_cast<int>(m_nChannels))
658 	{
659 		// There are enough Zxx commands, so let's assume this was made to be played with PixPlay
660 		Patterns.ForEachModCommand([](ModCommand &m)
661 		{
662 			if(m.command == CMD_MIDI)
663 			{
664 				m.command = CMD_S3MCMDEX;
665 				m.param |= 0x80;
666 			}
667 		});
668 	}
669 
670 	return true;
671 }
672 
673 
674 #ifndef MODPLUG_NO_FILESAVE
675 
SaveS3M(std::ostream & f) const676 bool CSoundFile::SaveS3M(std::ostream &f) const
677 {
678 	static constexpr uint8 filler[16] =
679 	{
680 		0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
681 		0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
682 	};
683 
684 	if(m_nChannels == 0)
685 	{
686 		return false;
687 	}
688 
689 	const bool saveMuteStatus =
690 #ifdef MODPLUG_TRACKER
691 		TrackerSettings::Instance().MiscSaveChannelMuteStatus;
692 #else
693 		true;
694 #endif
695 
696 	S3MFileHeader fileHeader;
697 	MemsetZero(fileHeader);
698 
699 	mpt::String::WriteBuf(mpt::String::nullTerminated, fileHeader.name) = m_songName;
700 	fileHeader.dosEof = S3MFileHeader::idEOF;
701 	fileHeader.fileType = S3MFileHeader::idS3MType;
702 
703 	// Orders
704 	ORDERINDEX writeOrders = Order().GetLengthTailTrimmed();
705 	if(writeOrders < 2)
706 	{
707 		writeOrders = 2;
708 	} else if((writeOrders % 2u) != 0)
709 	{
710 		// Number of orders should be even
711 		writeOrders++;
712 	}
713 	LimitMax(writeOrders, static_cast<ORDERINDEX>(256));
714 	fileHeader.ordNum = static_cast<uint16>(writeOrders);
715 
716 	// Samples
717 	SAMPLEINDEX writeSamples = static_cast<SAMPLEINDEX>(GetNumInstruments());
718 	if(writeSamples == 0)
719 	{
720 		writeSamples = GetNumSamples();
721 	}
722 	writeSamples = Clamp(writeSamples, static_cast<SAMPLEINDEX>(1), static_cast<SAMPLEINDEX>(99));
723 	fileHeader.smpNum = static_cast<uint16>(writeSamples);
724 
725 	// Patterns
726 	PATTERNINDEX writePatterns = std::min(Patterns.GetNumPatterns(), PATTERNINDEX(100));
727 	fileHeader.patNum = static_cast<uint16>(writePatterns);
728 
729 	// Flags
730 	if(m_SongFlags[SONG_FASTVOLSLIDES])
731 	{
732 		fileHeader.flags |= S3MFileHeader::fastVolumeSlides;
733 	}
734 	if(m_nMaxPeriod < 20000 || m_SongFlags[SONG_AMIGALIMITS])
735 	{
736 		fileHeader.flags |= S3MFileHeader::amigaLimits;
737 	}
738 	if(m_SongFlags[SONG_S3MOLDVIBRATO])
739 	{
740 		fileHeader.flags |= S3MFileHeader::st2Vibrato;
741 	}
742 
743 	// Version info following: ST3.20 = 0x1320
744 	// Most significant nibble = Tracker ID, see S3MFileHeader::S3MTrackerVersions
745 	// Following: One nibble = Major version, one byte = Minor version (hex)
746 	const uint32 mptVersion = Version::Current().GetRawVersion();
747 	fileHeader.cwtv = S3MFileHeader::trkOpenMPT | static_cast<uint16>((mptVersion >> 16) & S3MFileHeader::versionMask);
748 	fileHeader.reserved2 = static_cast<uint16>(mptVersion);
749 	fileHeader.formatVersion = S3MFileHeader::newVersion;
750 	memcpy(fileHeader.magic, "SCRM", 4);
751 
752 	// Song Variables
753 	fileHeader.globalVol = static_cast<uint8>(std::min(m_nDefaultGlobalVolume / 4u, uint32(64)));
754 	fileHeader.speed = static_cast<uint8>(Clamp(m_nDefaultSpeed, 1u, 254u));
755 	fileHeader.tempo = static_cast<uint8>(Clamp(m_nDefaultTempo.GetInt(), 33u, 255u));
756 	fileHeader.masterVolume = static_cast<uint8>(Clamp(m_nSamplePreAmp, 16u, 127u) | 0x80);
757 	fileHeader.ultraClicks = 16;
758 	fileHeader.usePanningTable = S3MFileHeader::idPanning;
759 
760 	mpt::IO::Write(f, fileHeader);
761 	Order().WriteAsByte(f, writeOrders);
762 
763 	// Comment about parapointers stolen from Schism Tracker:
764 	// The sample data parapointers are 24+4 bits, whereas pattern data and sample headers are only 16+4
765 	// bits -- so while the sample data can be written up to 268 MB within the file (starting at 0xffffff0),
766 	// the pattern data and sample headers are restricted to the first 1 MB (starting at 0xffff0). In effect,
767 	// this practically requires the sample data to be written last in the file, as it is entirely possible
768 	// (and quite easy, even) to write more than 1 MB of sample data in a file.
769 	// The "practical standard order" listed in TECH.DOC is sample headers, patterns, then sample data.
770 
771 	// Calculate offset of first sample header...
772 	mpt::IO::Offset sampleHeaderOffset = mpt::IO::TellWrite(f) + (writeSamples + writePatterns) * 2 + 32;
773 	// ...which must be a multiple of 16, because parapointers omit the lowest 4 bits.
774 	sampleHeaderOffset = (sampleHeaderOffset + 15) & ~15;
775 
776 	std::vector<uint16le> sampleOffsets(writeSamples);
777 	for(SAMPLEINDEX smp = 0; smp < writeSamples; smp++)
778 	{
779 		static_assert((sizeof(S3MSampleHeader) % 16) == 0);
780 		sampleOffsets[smp] = static_cast<uint16>((sampleHeaderOffset + smp * sizeof(S3MSampleHeader)) / 16);
781 	}
782 	mpt::IO::Write(f, sampleOffsets);
783 
784 	mpt::IO::Offset patternPointerOffset = mpt::IO::TellWrite(f);
785 	mpt::IO::Offset firstPatternOffset = sampleHeaderOffset + writeSamples * sizeof(S3MSampleHeader);
786 	std::vector<uint16le> patternOffsets(writePatterns);
787 
788 	// Need to calculate the real offsets later.
789 	mpt::IO::Write(f, patternOffsets);
790 
791 	// Write channel panning
792 	uint8 chnPan[32];
793 	for(CHANNELINDEX chn = 0; chn < 32; chn++)
794 	{
795 		if(chn < GetNumChannels())
796 			chnPan[chn] = static_cast<uint8>(((ChnSettings[chn].nPan * 15 + 128) / 256) | 0x20);
797 		else
798 			chnPan[chn] = 0x08;
799 	}
800 	mpt::IO::Write(f, chnPan);
801 
802 	// Do we need to fill up the file with some padding bytes for 16-Byte alignment?
803 	mpt::IO::Offset curPos = mpt::IO::TellWrite(f);
804 	if(curPos < sampleHeaderOffset)
805 	{
806 		MPT_ASSERT(sampleHeaderOffset - curPos < 16);
807 		mpt::IO::WriteRaw(f, filler, static_cast<std::size_t>(sampleHeaderOffset - curPos));
808 	}
809 
810 	// Don't write sample headers for now, we are lacking the sample offset data.
811 	mpt::IO::SeekAbsolute(f, firstPatternOffset);
812 
813 	// Write patterns
814 	enum class S3MChannelType : uint8 { kUnused = 0, kPCM = 1, kAdlib = 2 };
815 	FlagSet<S3MChannelType> channelType[32] = { S3MChannelType::kUnused };
816 	bool globalCmdOnMutedChn = false;
817 	for(PATTERNINDEX pat = 0; pat < writePatterns; pat++)
818 	{
819 		if(Patterns.IsPatternEmpty(pat))
820 		{
821 			patternOffsets[pat] = 0;
822 			continue;
823 		}
824 
825 		mpt::IO::Offset patOffset = mpt::IO::TellWrite(f);
826 		if(patOffset > 0xFFFF0)
827 		{
828 			AddToLog(LogError, MPT_UFORMAT("Too much pattern data! Writing patterns failed starting from pattern {}.")(pat));
829 			break;
830 		}
831 		MPT_ASSERT((patOffset % 16) == 0);
832 		patternOffsets[pat] = static_cast<uint16>(patOffset / 16);
833 
834 		std::vector<uint8> buffer;
835 		buffer.reserve(5 * 1024);
836 		// Reserve space for length bytes
837 		buffer.resize(2, 0);
838 
839 		if(Patterns.IsValidPat(pat))
840 		{
841 			for(ROWINDEX row = 0; row < 64; row++)
842 			{
843 				if(row >= Patterns[pat].GetNumRows())
844 				{
845 					// Invent empty row
846 					buffer.push_back(s3mEndOfRow);
847 					continue;
848 				}
849 
850 				const PatternRow rowBase = Patterns[pat].GetRow(row);
851 
852 				CHANNELINDEX writeChannels = std::min(CHANNELINDEX(32), GetNumChannels());
853 				for(CHANNELINDEX chn = 0; chn < writeChannels; chn++)
854 				{
855 					const ModCommand &m = rowBase[chn];
856 
857 					uint8 info = static_cast<uint8>(chn);
858 					uint8 note = m.note;
859 					ModCommand::VOLCMD volcmd = m.volcmd;
860 					uint8 vol = m.vol;
861 					uint8 command = m.command;
862 					uint8 param = m.param;
863 
864 					if(note != NOTE_NONE || m.instr != 0)
865 					{
866 						info |= s3mNotePresent;
867 
868 						if(note == NOTE_NONE)
869 						{
870 							note = s3mNoteNone;
871 						} else if(ModCommand::IsSpecialNote(note))
872 						{
873 							// Note Cut
874 							note = s3mNoteOff;
875 						} else if(note < 12 + NOTE_MIN)
876 						{
877 							// Too low
878 							note = 0;
879 						} else if(note <= NOTE_MAX)
880 						{
881 							note -= (12 + NOTE_MIN);
882 							note = (note % 12) + ((note / 12) << 4);
883 						}
884 
885 						if(m.instr > 0 && m.instr <= GetNumSamples())
886 						{
887 							const ModSample &smp = Samples[m.instr];
888 							if(smp.uFlags[CHN_ADLIB])
889 								channelType[chn].set(S3MChannelType::kAdlib);
890 							else if(smp.HasSampleData())
891 								channelType[chn].set(S3MChannelType::kPCM);
892 						}
893 					}
894 
895 					if(command == CMD_VOLUME)
896 					{
897 						command = CMD_NONE;
898 						volcmd = VOLCMD_VOLUME;
899 						vol = std::min(param, uint8(64));
900 					}
901 
902 					if(volcmd == VOLCMD_VOLUME)
903 					{
904 						info |= s3mVolumePresent;
905 					} else if(volcmd == VOLCMD_PANNING)
906 					{
907 						info |= s3mVolumePresent;
908 						vol |= 0x80;
909 					}
910 
911 					if(command != CMD_NONE)
912 					{
913 						S3MSaveConvert(command, param, false, true);
914 						if(command || param)
915 						{
916 							info |= s3mEffectPresent;
917 							if(saveMuteStatus && ChnSettings[chn].dwFlags[CHN_MUTE] && m.IsGlobalCommand())
918 							{
919 								globalCmdOnMutedChn = true;
920 							}
921 						}
922 					}
923 
924 					if(info & s3mAnyPresent)
925 					{
926 						buffer.push_back(info);
927 						if(info & s3mNotePresent)
928 						{
929 							buffer.push_back(note);
930 							buffer.push_back(m.instr);
931 						}
932 						if(info & s3mVolumePresent)
933 						{
934 							buffer.push_back(vol);
935 						}
936 						if(info & s3mEffectPresent)
937 						{
938 							buffer.push_back(command);
939 							buffer.push_back(param);
940 						}
941 					}
942 				}
943 
944 				buffer.push_back(s3mEndOfRow);
945 			}
946 		} else
947 		{
948 			// Invent empty pattern
949 			buffer.insert(buffer.end(), 64, s3mEndOfRow);
950 		}
951 
952 		uint16 length = mpt::saturate_cast<uint16>(buffer.size());
953 		buffer[0] = static_cast<uint8>(length & 0xFF);
954 		buffer[1] = static_cast<uint8>((length >> 8) & 0xFF);
955 
956 		if((buffer.size() % 16u) != 0)
957 		{
958 			// Add padding bytes
959 			buffer.insert(buffer.end(), 16 - (buffer.size() % 16u), 0);
960 		}
961 
962 		mpt::IO::Write(f, buffer);
963 	}
964 	if(globalCmdOnMutedChn)
965 	{
966 		//AddToLog(LogWarning, U_("Global commands on muted channels are interpreted only by some S3M players."));
967 	}
968 
969 	mpt::IO::Offset sampleDataOffset = mpt::IO::TellWrite(f);
970 
971 	// Write samples
972 	std::vector<S3MSampleHeader> sampleHeader(writeSamples);
973 
974 	for(SAMPLEINDEX smp = 0; smp < writeSamples; smp++)
975 	{
976 		SAMPLEINDEX realSmp = smp + 1;
977 		if(GetNumInstruments() != 0 && Instruments[smp] != nullptr)
978 		{
979 			// Find some valid sample associated with this instrument.
980 			for(SAMPLEINDEX keySmp : Instruments[smp]->Keyboard)
981 			{
982 				if(keySmp > 0 && keySmp <= GetNumSamples())
983 				{
984 					realSmp = keySmp;
985 					break;
986 				}
987 			}
988 		}
989 
990 		if(realSmp > GetNumSamples())
991 		{
992 			continue;
993 		}
994 
995 		const SmpLength smpLength = sampleHeader[smp].ConvertToS3M(Samples[realSmp]);
996 		mpt::String::WriteBuf(mpt::String::nullTerminated, sampleHeader[smp].name) = m_szNames[realSmp];
997 
998 		if(smpLength != 0)
999 		{
1000 			// Write sample data
1001 			if(sampleDataOffset > 0xFFFFFF0)
1002 			{
1003 				AddToLog(LogError, MPT_UFORMAT("Too much sample data! Writing samples failed starting from sample {}.")(realSmp));
1004 				break;
1005 			}
1006 
1007 			sampleHeader[smp].dataPointer[1] = static_cast<uint8>((sampleDataOffset >> 4) & 0xFF);
1008 			sampleHeader[smp].dataPointer[2] = static_cast<uint8>((sampleDataOffset >> 12) & 0xFF);
1009 			sampleHeader[smp].dataPointer[0] = static_cast<uint8>((sampleDataOffset >> 20) & 0xFF);
1010 
1011 			size_t writtenLength = sampleHeader[smp].GetSampleFormat(false).WriteSample(f, Samples[realSmp], smpLength);
1012 			sampleDataOffset += writtenLength;
1013 			if((writtenLength % 16u) != 0)
1014 			{
1015 				size_t fillSize = 16 - (writtenLength % 16u);
1016 				mpt::IO::WriteRaw(f, filler, fillSize);
1017 				sampleDataOffset += fillSize;
1018 			}
1019 		}
1020 	}
1021 
1022 	// Channel Table
1023 	uint8 sampleCh = 0, adlibCh = 0;
1024 	for(CHANNELINDEX chn = 0; chn < 32; chn++)
1025 	{
1026 		if(chn < GetNumChannels())
1027 		{
1028 			if(channelType[chn][S3MChannelType::kPCM] && channelType[chn][S3MChannelType::kAdlib])
1029 			{
1030 				AddToLog(LogWarning, MPT_UFORMAT("Pattern channel {} constains both samples and OPL instruments, which is not supported by Scream Tracker 3.")(chn + 1));
1031 			}
1032 			// ST3 only supports 16 PCM channels, so if channels 17-32 are used,
1033 			// they must be mapped to the same "internal channels" as channels 1-16.
1034 			// The channel indices determine in which order channels are evaluated in ST3.
1035 			// First, the "left" channels (0...7) are evaluated, then the "right" channels (8...15).
1036 			// Previously, an alternating LRLR scheme was written, which would lead to a different
1037 			// effect processing in ST3 than LLL...RRR, but since OpenMPT doesn't care about the
1038 			// channel order and always parses them left to right as they appear in the pattern,
1039 			// we should just write in the LLL...RRR manner.
1040 			uint8 ch = sampleCh % 16u; // If there are neither PCM nor AdLib instruments on this channel, just fall back a regular sample-based channel for maximum compatibility.
1041 			if(channelType[chn][S3MChannelType::kPCM])
1042 				ch = (sampleCh++) % 16u;
1043 			else if(channelType[chn][S3MChannelType::kAdlib])
1044 				ch = 16 + ((adlibCh++) % 9u);
1045 
1046 			if(saveMuteStatus && ChnSettings[chn].dwFlags[CHN_MUTE])
1047 			{
1048 				ch |= 0x80;
1049 			}
1050 			fileHeader.channels[chn] = ch;
1051 		} else
1052 		{
1053 			fileHeader.channels[chn] = 0xFF;
1054 		}
1055 	}
1056 	if(sampleCh > 16)
1057 	{
1058 		AddToLog(LogWarning, MPT_UFORMAT("This module has more than 16 ({}) sample channels, which is not supported by Scream Tracker 3.")(sampleCh));
1059 	}
1060 	if(adlibCh > 9)
1061 	{
1062 		AddToLog(LogWarning, MPT_UFORMAT("This module has more than 9 ({}) OPL channels, which is not supported by Scream Tracker 3.")(adlibCh));
1063 	}
1064 
1065 	mpt::IO::SeekAbsolute(f, 0);
1066 	mpt::IO::Write(f, fileHeader);
1067 
1068 	// Now we know where the patterns are.
1069 	if(writePatterns != 0)
1070 	{
1071 		mpt::IO::SeekAbsolute(f, patternPointerOffset);
1072 		mpt::IO::Write(f, patternOffsets);
1073 	}
1074 
1075 	// And we can finally write the sample headers.
1076 	if(writeSamples != 0)
1077 	{
1078 		mpt::IO::SeekAbsolute(f, sampleHeaderOffset);
1079 		mpt::IO::Write(f, sampleHeader);
1080 	}
1081 
1082 	return true;
1083 }
1084 
1085 #endif // MODPLUG_NO_FILESAVE
1086 
1087 
1088 OPENMPT_NAMESPACE_END
1089