1 /*
2  * Load_ult.cpp
3  * ------------
4  * Purpose: ULT (UltraTracker) module loader
5  * Notes  : (currently none)
6  * Authors: Storlek (Original author - http://schismtracker.org/ - code ported with permission)
7  *			Johannes Schultz (OpenMPT Port, tweaks)
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 
15 OPENMPT_NAMESPACE_BEGIN
16 
17 struct UltFileHeader
18 {
19 	char  signature[14];		// "MAS_UTrack_V00"
20 	uint8 version;				// '1'...'4'
21 	char  songName[32];			// Song Name, not guaranteed to be null-terminated
22 	uint8 messageLength;		// Number of Lines
23 };
24 
25 MPT_BINARY_STRUCT(UltFileHeader, 48)
26 
27 
28 struct UltSample
29 {
30 	enum UltSampleFlags
31 	{
32 		ULT_16BIT = 4,
33 		ULT_LOOP  = 8,
34 		ULT_PINGPONGLOOP = 16,
35 	};
36 
37 	char     name[32];
38 	char     filename[12];
39 	uint32le loopStart;
40 	uint32le loopEnd;
41 	uint32le sizeStart;
42 	uint32le sizeEnd;
43 	uint8le  volume;	// 0-255, apparently prior to 1.4 this was logarithmic?
44 	uint8le  flags;		// above
45 	uint16le speed;		// only exists for 1.4+
46 	int16le  finetune;
47 
48 	// Convert an ULT sample header to OpenMPT's internal sample header.
ConvertToMPTUltSample49 	void ConvertToMPT(ModSample &mptSmp) const
50 	{
51 		mptSmp.Initialize();
52 		mptSmp.Set16BitCuePoints();
53 
54 		mptSmp.filename = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, filename);
55 
56 		if(sizeEnd <= sizeStart)
57 		{
58 			return;
59 		}
60 
61 		mptSmp.nLength = sizeEnd - sizeStart;
62 		mptSmp.nSustainStart = loopStart;
63 		mptSmp.nSustainEnd = std::min(static_cast<SmpLength>(loopEnd), mptSmp.nLength);
64 		mptSmp.nVolume = volume;
65 
66 		mptSmp.nC5Speed = speed;
67 		if(finetune)
68 		{
69 			mptSmp.Transpose(finetune / (12.0 * 32768.0));
70 		}
71 
72 		if(flags & ULT_LOOP)
73 			mptSmp.uFlags.set(CHN_SUSTAINLOOP);
74 		if(flags & ULT_PINGPONGLOOP)
75 			mptSmp.uFlags.set(CHN_PINGPONGSUSTAIN);
76 		if(flags & ULT_16BIT)
77 		{
78 			mptSmp.uFlags.set(CHN_16BIT);
79 			mptSmp.nSustainStart /= 2;
80 			mptSmp.nSustainEnd /= 2;
81 		}
82 
83 	}
84 };
85 
86 MPT_BINARY_STRUCT(UltSample, 66)
87 
88 
89 /* Unhandled effects:
90 5x1 - do not loop sample (x is unused)
91 E0x - set vibrato strength (2 is normal)
92 
93 The logarithmic volume scale used in older format versions here, or pretty
94 much anywhere for that matter. I don't even think Ultra Tracker tries to
95 convert them. */
96 
97 
TranslateULTCommands(uint8 & effect,uint8 & param,uint8 version)98 static void TranslateULTCommands(uint8 &effect, uint8 &param, uint8 version)
99 {
100 
101 	static constexpr uint8 ultEffTrans[] =
102 	{
103 		CMD_ARPEGGIO,
104 		CMD_PORTAMENTOUP,
105 		CMD_PORTAMENTODOWN,
106 		CMD_TONEPORTAMENTO,
107 		CMD_VIBRATO,
108 		CMD_NONE,
109 		CMD_NONE,
110 		CMD_TREMOLO,
111 		CMD_NONE,
112 		CMD_OFFSET,
113 		CMD_VOLUMESLIDE,
114 		CMD_PANNING8,
115 		CMD_VOLUME,
116 		CMD_PATTERNBREAK,
117 		CMD_NONE, // extended effects, processed separately
118 		CMD_SPEED,
119 	};
120 
121 
122 	uint8 e = effect & 0x0F;
123 	effect = ultEffTrans[e];
124 
125 	switch(e)
126 	{
127 	case 0x00:
128 		if(!param || version < '3')
129 			effect = CMD_NONE;
130 		break;
131 	case 0x05:
132 		// play backwards
133 		if((param & 0x0F) == 0x02 || (param & 0xF0) == 0x20)
134 		{
135 			effect = CMD_S3MCMDEX;
136 			param = 0x9F;
137 		}
138 		if(((param & 0x0F) == 0x0C || (param & 0xF0) == 0xC0) && version >= '3')
139 		{
140 			effect = CMD_KEYOFF;
141 			param = 0;
142 		}
143 		break;
144 	case 0x07:
145 		if(version < '4')
146 			effect = CMD_NONE;
147 		break;
148 	case 0x0A:
149 		if(param & 0xF0)
150 			param &= 0xF0;
151 		break;
152 	case 0x0B:
153 		param = (param & 0x0F) * 0x11;
154 		break;
155 	case 0x0C: // volume
156 		param /= 4u;
157 		break;
158 	case 0x0D: // pattern break
159 		param = 10 * (param >> 4) + (param & 0x0F);
160 		break;
161 	case 0x0E: // special
162 		switch(param >> 4)
163 		{
164 		case 0x01:
165 			effect = CMD_PORTAMENTOUP;
166 			param = 0xF0 | (param & 0x0F);
167 			break;
168 		case 0x02:
169 			effect = CMD_PORTAMENTODOWN;
170 			param = 0xF0 | (param & 0x0F);
171 			break;
172 		case 0x08:
173 			if(version >= '4')
174 			{
175 				effect = CMD_S3MCMDEX;
176 				param = 0x60 | (param & 0x0F);
177 			}
178 			break;
179 		case 0x09:
180 			effect = CMD_RETRIG;
181 			param &= 0x0F;
182 			break;
183 		case 0x0A:
184 			effect = CMD_VOLUMESLIDE;
185 			param = ((param & 0x0F) << 4) | 0x0F;
186 			break;
187 		case 0x0B:
188 			effect = CMD_VOLUMESLIDE;
189 			param = 0xF0 | (param & 0x0F);
190 			break;
191 		case 0x0C: case 0x0D:
192 			effect = CMD_S3MCMDEX;
193 			break;
194 		}
195 		break;
196 	case 0x0F:
197 		if(param > 0x2F)
198 			effect = CMD_TEMPO;
199 		break;
200 	}
201 }
202 
203 
ReadULTEvent(ModCommand & m,FileReader & file,uint8 version)204 static int ReadULTEvent(ModCommand &m, FileReader &file, uint8 version)
205 {
206 	uint8 repeat = 1;
207 	uint8 b = file.ReadUint8();
208 	if(b == 0xFC)	// repeat event
209 	{
210 		repeat = file.ReadUint8();
211 		b = file.ReadUint8();
212 	}
213 
214 	m.note = (b > 0 && b < 61) ? (b + 35 + NOTE_MIN) : NOTE_NONE;
215 
216 	const auto [instr, cmd, para1, para2] = file.ReadArray<uint8, 4>();
217 
218 	m.instr = instr;
219 	uint8 cmd1 = cmd & 0x0F;
220 	uint8 cmd2 = cmd >> 4;
221 	uint8 param1 = para1;
222 	uint8 param2 = para2;
223 	TranslateULTCommands(cmd1, param1, version);
224 	TranslateULTCommands(cmd2, param2, version);
225 
226 	// sample offset -- this is even more special than digitrakker's
227 	if(cmd1 == CMD_OFFSET && cmd2 == CMD_OFFSET)
228 	{
229 		uint32 offset = ((param2 << 8) | param1) >> 6;
230 		m.command = CMD_OFFSET;
231 		m.param = static_cast<ModCommand::PARAM>(offset);
232 		if(offset > 0xFF)
233 		{
234 			m.volcmd = VOLCMD_OFFSET;
235 			m.vol = static_cast<ModCommand::VOL>(offset >> 8);
236 		}
237 		return repeat;
238 	} else if(cmd1 == CMD_OFFSET)
239 	{
240 		uint32 offset = param1 * 4;
241 		param1 = mpt::saturate_cast<uint8>(offset);
242 		if(offset > 0xFF && ModCommand::GetEffectWeight(cmd2) < ModCommand::GetEffectType(CMD_OFFSET))
243 		{
244 			m.command = CMD_OFFSET;
245 			m.param = static_cast<ModCommand::PARAM>(offset);
246 			m.volcmd = VOLCMD_OFFSET;
247 			m.vol = static_cast<ModCommand::VOL>(offset >> 8);
248 			return repeat;
249 		}
250 	} else if(cmd2 == CMD_OFFSET)
251 	{
252 		uint32 offset = param2 * 4;
253 		param2 = mpt::saturate_cast<uint8>(offset);
254 		if(offset > 0xFF && ModCommand::GetEffectWeight(cmd1) < ModCommand::GetEffectType(CMD_OFFSET))
255 		{
256 			m.command = CMD_OFFSET;
257 			m.param = static_cast<ModCommand::PARAM>(offset);
258 			m.volcmd = VOLCMD_OFFSET;
259 			m.vol = static_cast<ModCommand::VOL>(offset >> 8);
260 			return repeat;
261 		}
262 	} else if(cmd1 == cmd2)
263 	{
264 		// don't try to figure out how ultratracker does this, it's quite random
265 		cmd2 = CMD_NONE;
266 	}
267 	if(cmd2 == CMD_VOLUME || (cmd2 == CMD_NONE && cmd1 != CMD_VOLUME))
268 	{
269 		// swap commands
270 		std::swap(cmd1, cmd2);
271 		std::swap(param1, param2);
272 	}
273 
274 	// Combine slide commands, if possible
275 	ModCommand::CombineEffects(cmd2, param2, cmd1, param1);
276 	ModCommand::TwoRegularCommandsToMPT(cmd1, param1, cmd2, param2);
277 
278 	m.volcmd = cmd1;
279 	m.vol = param1;
280 	m.command = cmd2;
281 	m.param = param2;
282 
283 	return repeat;
284 }
285 
286 
287 // Functor for postfixing ULT patterns (this is easier than just remembering everything WHILE we're reading the pattern events)
288 struct PostFixUltCommands
289 {
PostFixUltCommandsPostFixUltCommands290 	PostFixUltCommands(CHANNELINDEX numChannels)
291 	{
292 		this->numChannels = numChannels;
293 		curChannel = 0;
294 		writeT125 = false;
295 		isPortaActive.resize(numChannels, false);
296 	}
297 
operator ()PostFixUltCommands298 	void operator()(ModCommand &m)
299 	{
300 		// Attempt to fix portamentos.
301 		// UltraTracker will slide until the destination note is reached or 300 is encountered.
302 
303 		// Stop porta?
304 		if(m.command == CMD_TONEPORTAMENTO && m.param == 0)
305 		{
306 			isPortaActive[curChannel] = false;
307 			m.command = CMD_NONE;
308 		}
309 		if(m.volcmd == VOLCMD_TONEPORTAMENTO && m.vol == 0)
310 		{
311 			isPortaActive[curChannel] = false;
312 			m.volcmd = VOLCMD_NONE;
313 		}
314 
315 		// Apply porta?
316 		if(m.note == NOTE_NONE && isPortaActive[curChannel])
317 		{
318 			if(m.command == CMD_NONE && m.volcmd != VOLCMD_TONEPORTAMENTO)
319 			{
320 				m.command = CMD_TONEPORTAMENTO;
321 				m.param = 0;
322 			} else if(m.volcmd == VOLCMD_NONE && m.command != CMD_TONEPORTAMENTO)
323 			{
324 				m.volcmd = VOLCMD_TONEPORTAMENTO;
325 				m.vol = 0;
326 			}
327 		} else	// new note -> stop porta (or initialize again)
328 		{
329 			isPortaActive[curChannel] = (m.command == CMD_TONEPORTAMENTO || m.volcmd == VOLCMD_TONEPORTAMENTO);
330 		}
331 
332 		// attempt to fix F00 (reset to tempo 125, speed 6)
333 		if(writeT125 && m.command == CMD_NONE)
334 		{
335 			m.command = CMD_TEMPO;
336 			m.param = 125;
337 		}
338 		if(m.command == CMD_SPEED && m.param == 0)
339 		{
340 			m.param = 6;
341 			writeT125 = true;
342 		}
343 		if(m.command == CMD_TEMPO)	// don't try to fix this anymore if the tempo has already changed.
344 		{
345 			writeT125 = false;
346 		}
347 		curChannel = (curChannel + 1) % numChannels;
348 	}
349 
350 	std::vector<bool> isPortaActive;
351 	CHANNELINDEX numChannels, curChannel;
352 	bool writeT125;
353 };
354 
355 
ValidateHeader(const UltFileHeader & fileHeader)356 static bool ValidateHeader(const UltFileHeader &fileHeader)
357 {
358 	if(fileHeader.version < '1'
359 		|| fileHeader.version > '4'
360 		|| std::memcmp(fileHeader.signature, "MAS_UTrack_V00", sizeof(fileHeader.signature))
361 		)
362 	{
363 		return false;
364 	}
365 	return true;
366 }
367 
GetHeaderMinimumAdditionalSize(const UltFileHeader & fileHeader)368 static uint64 GetHeaderMinimumAdditionalSize(const UltFileHeader &fileHeader)
369 {
370 	return fileHeader.messageLength * 32u + 3u + 256u;
371 }
372 
ProbeFileHeaderULT(MemoryFileReader file,const uint64 * pfilesize)373 CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderULT(MemoryFileReader file, const uint64 *pfilesize)
374 {
375 	UltFileHeader fileHeader;
376 	if(!file.ReadStruct(fileHeader))
377 	{
378 		return ProbeWantMoreData;
379 	}
380 	if(!ValidateHeader(fileHeader))
381 	{
382 		return ProbeFailure;
383 	}
384 	return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
385 }
386 
387 
ReadULT(FileReader & file,ModLoadingFlags loadFlags)388 bool CSoundFile::ReadULT(FileReader &file, ModLoadingFlags loadFlags)
389 {
390 	file.Rewind();
391 
392 	UltFileHeader fileHeader;
393 	if(!file.ReadStruct(fileHeader))
394 	{
395 		return false;
396 	}
397 	if(!ValidateHeader(fileHeader))
398 	{
399 		return false;
400 	}
401 	if(loadFlags == onlyVerifyHeader)
402 	{
403 		return true;
404 	}
405 	if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
406 	{
407 		return false;
408 	}
409 
410 	InitializeGlobals(MOD_TYPE_ULT);
411 	m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songName);
412 
413 	const mpt::uchar *versions[] = {UL_("<1.4"), UL_("1.4"), UL_("1.5"), UL_("1.6")};
414 	m_modFormat.formatName = U_("UltraTracker");
415 	m_modFormat.type = U_("ult");
416 	m_modFormat.madeWithTracker = U_("UltraTracker ") + versions[fileHeader.version - '1'];
417 	m_modFormat.charset = mpt::Charset::CP437;
418 
419 	m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS;  // this will be converted to IT format by MPT.
420 
421 	// Read "messageLength" lines, each containing 32 characters.
422 	m_songMessage.ReadFixedLineLength(file, fileHeader.messageLength * 32, 32, 0);
423 
424 	if(SAMPLEINDEX numSamples = file.ReadUint8(); numSamples < MAX_SAMPLES)
425 		m_nSamples = numSamples;
426 	else
427 		return false;
428 
429 	for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
430 	{
431 		UltSample sampleHeader;
432 
433 		// Annoying: v4 added a field before the end of the struct
434 		if(fileHeader.version >= '4')
435 		{
436 			file.ReadStruct(sampleHeader);
437 		} else
438 		{
439 			file.ReadStructPartial(sampleHeader, 64);
440 			sampleHeader.finetune = sampleHeader.speed;
441 			sampleHeader.speed = 8363;
442 		}
443 
444 		sampleHeader.ConvertToMPT(Samples[smp]);
445 		m_szNames[smp] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sampleHeader.name);
446 	}
447 
448 	ReadOrderFromFile<uint8>(Order(), file, 256, 0xFF, 0xFE);
449 
450 	if(CHANNELINDEX numChannels = file.ReadUint8() + 1u; numChannels <= MAX_BASECHANNELS)
451 		m_nChannels = numChannels;
452 	else
453 		return false;
454 
455 	PATTERNINDEX numPats = file.ReadUint8() + 1;
456 
457 	for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++)
458 	{
459 		ChnSettings[chn].Reset();
460 		if(fileHeader.version >= '3')
461 			ChnSettings[chn].nPan = ((file.ReadUint8() & 0x0F) << 4) + 8;
462 		else
463 			ChnSettings[chn].nPan = (chn & 1) ? 192 : 64;
464 	}
465 
466 	Patterns.ResizeArray(numPats);
467 	for(PATTERNINDEX pat = 0; pat < numPats; pat++)
468 	{
469 		if(!Patterns.Insert(pat, 64))
470 			return false;
471 	}
472 
473 	for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
474 	{
475 		ModCommand evnote;
476 		for(PATTERNINDEX pat = 0; pat < numPats && file.CanRead(5); pat++)
477 		{
478 			ModCommand *note = Patterns[pat].GetpModCommand(0, chn);
479 			ROWINDEX row = 0;
480 			while(row < 64)
481 			{
482 				int repeat = ReadULTEvent(evnote, file, fileHeader.version);
483 				if(repeat + row > 64)
484 					repeat = 64 - row;
485 				if(repeat == 0) break;
486 				while(repeat--)
487 				{
488 					*note = evnote;
489 					note += GetNumChannels();
490 					row++;
491 				}
492 			}
493 		}
494 	}
495 
496 	// Post-fix some effects.
497 	Patterns.ForEachModCommand(PostFixUltCommands(GetNumChannels()));
498 
499 	if(loadFlags & loadSampleData)
500 	{
501 		for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
502 		{
503 			SampleIO(
504 				Samples[smp].uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit,
505 				SampleIO::mono,
506 				SampleIO::littleEndian,
507 				SampleIO::signedPCM)
508 				.ReadSample(Samples[smp], file);
509 		}
510 	}
511 	return true;
512 }
513 
514 
515 OPENMPT_NAMESPACE_END
516