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 ¶m, 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