1 /*
2 * Load_mdl.cpp
3 * ------------
4 * Purpose: Digitrakker (MDL) module loader
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
14 OPENMPT_NAMESPACE_BEGIN
15
16 // MDL file header
17 struct MDLFileHeader
18 {
19 char id[4]; // "DMDL"
20 uint8 version;
21 };
22
23 MPT_BINARY_STRUCT(MDLFileHeader, 5)
24
25
26 // RIFF-style Chunk
27 struct MDLChunk
28 {
29 // 16-Bit chunk identifiers
30 enum ChunkIdentifiers
31 {
32 idInfo = MagicLE("IN"),
33 idMessage = MagicLE("ME"),
34 idPats = MagicLE("PA"),
35 idPatNames = MagicLE("PN"),
36 idTracks = MagicLE("TR"),
37 idInstrs = MagicLE("II"),
38 idVolEnvs = MagicLE("VE"),
39 idPanEnvs = MagicLE("PE"),
40 idFreqEnvs = MagicLE("FE"),
41 idSampleInfo = MagicLE("IS"),
42 ifSampleData = MagicLE("SA"),
43 };
44
45 uint16le id;
46 uint32le length;
47
GetLengthMDLChunk48 size_t GetLength() const
49 {
50 return length;
51 }
52
GetIDMDLChunk53 ChunkIdentifiers GetID() const
54 {
55 return static_cast<ChunkIdentifiers>(id.get());
56 }
57 };
58
59 MPT_BINARY_STRUCT(MDLChunk, 6)
60
61
62 struct MDLInfoBlock
63 {
64 char title[32];
65 char composer[20];
66 uint16le numOrders;
67 uint16le restartPos;
68 uint8le globalVol; // 1...255
69 uint8le speed; // 1...255
70 uint8le tempo; // 4...255
71 uint8le chnSetup[32];
72 };
73
74 MPT_BINARY_STRUCT(MDLInfoBlock, 91)
75
76
77 // Sample header in II block
78 struct MDLSampleHeader
79 {
80 uint8le smpNum;
81 uint8le lastNote;
82 uint8le volume;
83 uint8le volEnvFlags; // 6 bits env #, 2 bits flags
84 uint8le panning;
85 uint8le panEnvFlags;
86 uint16le fadeout;
87 uint8le vibSpeed;
88 uint8le vibDepth;
89 uint8le vibSweep;
90 uint8le vibType;
91 uint8le reserved; // zero
92 uint8le freqEnvFlags;
93 };
94
95 MPT_BINARY_STRUCT(MDLSampleHeader, 14)
96
97
98 struct MDLEnvelope
99 {
100 uint8 envNum;
101 struct
102 {
103 uint8 x; // Delta value from last point, 0 means no more points defined
104 uint8 y; // 0...63
105 } nodes[15];
106 uint8 flags;
107 uint8 loop; // Lower 4 bits = start, upper 4 bits = end
108
ConvertToMPTMDLEnvelope109 void ConvertToMPT(InstrumentEnvelope &mptEnv) const
110 {
111 mptEnv.dwFlags.reset();
112 mptEnv.clear();
113 mptEnv.reserve(15);
114 int16 tick = -nodes[0].x;
115 for(uint8 n = 0; n < 15; n++)
116 {
117 if(!nodes[n].x)
118 break;
119 tick += nodes[n].x;
120 mptEnv.push_back(EnvelopeNode(tick, std::min(nodes[n].y, uint8(64)))); // actually 0-63
121 }
122
123 mptEnv.nLoopStart = (loop & 0x0F);
124 mptEnv.nLoopEnd = (loop >> 4);
125 mptEnv.nSustainStart = mptEnv.nSustainEnd = (flags & 0x0F);
126
127 if(flags & 0x10) mptEnv.dwFlags.set(ENV_SUSTAIN);
128 if(flags & 0x20) mptEnv.dwFlags.set(ENV_LOOP);
129 }
130 };
131
132 MPT_BINARY_STRUCT(MDLEnvelope, 33)
133
134
135 struct MDLPatternHeader
136 {
137 uint8le channels;
138 uint8le lastRow;
139 char name[16];
140 };
141
142 MPT_BINARY_STRUCT(MDLPatternHeader, 18)
143
144
145 enum
146 {
147 MDLNOTE_NOTE = 1 << 0,
148 MDLNOTE_SAMPLE = 1 << 1,
149 MDLNOTE_VOLUME = 1 << 2,
150 MDLNOTE_EFFECTS = 1 << 3,
151 MDLNOTE_PARAM1 = 1 << 4,
152 MDLNOTE_PARAM2 = 1 << 5,
153 };
154
155
156 static constexpr VibratoType MDLVibratoType[] = { VIB_SINE, VIB_RAMP_DOWN, VIB_SQUARE, VIB_SINE };
157
158 static constexpr ModCommand::COMMAND MDLEffTrans[] =
159 {
160 /* 0 */ CMD_NONE,
161 /* 1st column only */
162 /* 1 */ CMD_PORTAMENTOUP,
163 /* 2 */ CMD_PORTAMENTODOWN,
164 /* 3 */ CMD_TONEPORTAMENTO,
165 /* 4 */ CMD_VIBRATO,
166 /* 5 */ CMD_ARPEGGIO,
167 /* 6 */ CMD_NONE,
168 /* Either column */
169 /* 7 */ CMD_TEMPO,
170 /* 8 */ CMD_PANNING8,
171 /* 9 */ CMD_SETENVPOSITION,
172 /* A */ CMD_NONE,
173 /* B */ CMD_POSITIONJUMP,
174 /* C */ CMD_GLOBALVOLUME,
175 /* D */ CMD_PATTERNBREAK,
176 /* E */ CMD_S3MCMDEX,
177 /* F */ CMD_SPEED,
178 /* 2nd column only */
179 /* G */ CMD_VOLUMESLIDE, // up
180 /* H */ CMD_VOLUMESLIDE, // down
181 /* I */ CMD_RETRIG,
182 /* J */ CMD_TREMOLO,
183 /* K */ CMD_TREMOR,
184 /* L */ CMD_NONE,
185 };
186
187
188 // receive an MDL effect, give back a 'normal' one.
ConvertMDLCommand(uint8 & cmd,uint8 & param)189 static void ConvertMDLCommand(uint8 &cmd, uint8 ¶m)
190 {
191 if(cmd >= std::size(MDLEffTrans))
192 return;
193
194 uint8 origCmd = cmd;
195 cmd = MDLEffTrans[cmd];
196
197 switch(origCmd)
198 {
199 #ifdef MODPLUG_TRACKER
200 case 0x07: // Tempo
201 // MDL supports any nonzero tempo value, but OpenMPT doesn't
202 param = std::max(param, uint8(0x20));
203 break;
204 #endif // MODPLUG_TRACKER
205 case 0x08: // Panning
206 param = (param & 0x7F) * 2u;
207 break;
208 case 0x0C: // Global volume
209 param = (param + 1) / 2u;
210 break;
211 case 0x0D: // Pattern Break
212 // Convert from BCD
213 param = 10 * (param >> 4) + (param & 0x0F);
214 break;
215 case 0x0E: // Special
216 switch(param >> 4)
217 {
218 case 0x0: // unused
219 case 0x3: // unused
220 case 0x8: // Set Samplestatus (loop type)
221 cmd = CMD_NONE;
222 break;
223 case 0x1: // Pan Slide Left
224 cmd = CMD_PANNINGSLIDE;
225 param = (std::min(static_cast<uint8>(param & 0x0F), uint8(0x0E)) << 4) | 0x0F;
226 break;
227 case 0x2: // Pan Slide Right
228 cmd = CMD_PANNINGSLIDE;
229 param = 0xF0 | std::min(static_cast<uint8>(param & 0x0F), uint8(0x0E));
230 break;
231 case 0x4: // Vibrato Waveform
232 param = 0x30 | (param & 0x0F);
233 break;
234 case 0x5: // Set Finetune
235 cmd = CMD_FINETUNE;
236 param = (param << 4) ^ 0x80;
237 break;
238 case 0x6: // Pattern Loop
239 param = 0xB0 | (param & 0x0F);
240 break;
241 case 0x7: // Tremolo Waveform
242 param = 0x40 | (param & 0x0F);
243 break;
244 case 0x9: // Retrig
245 cmd = CMD_RETRIG;
246 param &= 0x0F;
247 break;
248 case 0xA: // Global vol slide up
249 cmd = CMD_GLOBALVOLSLIDE;
250 param = 0xF0 & (((param & 0x0F) + 1) << 3);
251 break;
252 case 0xB: // Global vol slide down
253 cmd = CMD_GLOBALVOLSLIDE;
254 param = ((param & 0x0F) + 1) >> 1;
255 break;
256 case 0xC: // Note cut
257 case 0xD: // Note delay
258 case 0xE: // Pattern delay
259 // Nothing to change here
260 break;
261 case 0xF: // Offset -- further mangled later.
262 cmd = CMD_OFFSET;
263 break;
264 }
265 break;
266 case 0x10: // Volslide up
267 if(param < 0xE0)
268 {
269 // 00...DF regular slide - four times more precise than in XM
270 param >>= 2;
271 if(param > 0x0F)
272 param = 0x0F;
273 param <<= 4;
274 } else if(param < 0xF0)
275 {
276 // E0...EF extra fine slide (on first tick, 4 times finer)
277 param = (((param & 0x0F) << 2) | 0x0F);
278 } else
279 {
280 // F0...FF regular fine slide (on first tick) - like in XM
281 param = ((param << 4) | 0x0F);
282 }
283 break;
284 case 0x11: // Volslide down
285 if(param < 0xE0)
286 {
287 // 00...DF regular slide - four times more precise than in XM
288 param >>= 2;
289 if(param > 0x0F)
290 param = 0x0F;
291 } else if(param < 0xF0)
292 {
293 // E0...EF extra fine slide (on first tick, 4 times finer)
294 param = (((param & 0x0F) >> 2) | 0xF0);
295 } else
296 {
297 // F0...FF regular fine slide (on first tick) - like in XM
298 }
299 break;
300 }
301 }
302
303
304 // Returns true if command was lost
ImportMDLCommands(ModCommand & m,uint8 vol,uint8 e1,uint8 e2,uint8 p1,uint8 p2)305 static bool ImportMDLCommands(ModCommand &m, uint8 vol, uint8 e1, uint8 e2, uint8 p1, uint8 p2)
306 {
307 // Map second effect values 1-6 to effects G-L
308 if(e2 >= 1 && e2 <= 6)
309 e2 += 15;
310
311 ConvertMDLCommand(e1, p1);
312 ConvertMDLCommand(e2, p2);
313 /* From the Digitrakker documentation:
314 * EFx -xx - Set Sample Offset
315 This is a double-command. It starts the
316 sample at adress xxx*256.
317 Example: C-5 01 -- EF1 -23 ->starts sample
318 01 at address 12300 (in hex).
319 Kind of screwy, but I guess it's better than the mess required to do it with IT (which effectively
320 requires 3 rows in order to set the offset past 0xff00). If we had access to the entire track, we
321 *might* be able to shove the high offset SAy into surrounding rows (or 2x MPTM #xx), but it wouldn't
322 always be possible, it'd make the loader a lot uglier, and generally would be more trouble than
323 it'd be worth to implement.
324
325 What's more is, if there's another effect in the second column, it's ALSO processed in addition to the
326 offset, and the second data byte is shared between the two effects. */
327 uint32 offset = uint32_max;
328 uint8 otherCmd = CMD_NONE;
329 if(e1 == CMD_OFFSET)
330 {
331 // EFy -xx => offset yxx00
332 offset = ((p1 & 0x0F) << 8) | p2;
333 p1 = (p1 & 0x0F) ? 0xFF : p2;
334 if(e2 == CMD_OFFSET)
335 e2 = CMD_NONE;
336 else
337 otherCmd = e2;
338 } else if (e2 == CMD_OFFSET)
339 {
340 // --- EFy => offset y0000
341 offset = (p2 & 0x0F) << 8;
342 p2 = (p2 & 0x0F) ? 0xFF : 0;
343 otherCmd = e1;
344 }
345
346 if(offset != uint32_max && offset > 0xFF && ModCommand::GetEffectWeight(otherCmd) < ModCommand::GetEffectWeight(CMD_OFFSET))
347 {
348 m.command = CMD_OFFSET;
349 m.param = static_cast<ModCommand::PARAM>(offset & 0xFF);
350 m.volcmd = VOLCMD_OFFSET;
351 m.vol = static_cast<ModCommand::VOL>(offset >> 8);
352 return otherCmd != CMD_NONE || vol != 0;
353 }
354
355 if(vol)
356 {
357 m.volcmd = VOLCMD_VOLUME;
358 m.vol = (vol + 2) / 4u;
359 }
360
361 // If we have Dxx + G00, or Dxx + H00, combine them into Lxx/Kxx.
362 ModCommand::CombineEffects(e1, p1, e2, p2);
363
364 bool lostCommand = false;
365 // Try to fit the "best" effect into e2.
366 if(e1 == CMD_NONE)
367 {
368 // Easy
369 } else if(e2 == CMD_NONE)
370 {
371 // Almost as easy
372 e2 = e1;
373 p2 = p1;
374 e1 = CMD_NONE;
375 } else if(e1 == e2 && e1 != CMD_S3MCMDEX)
376 {
377 // Digitrakker processes the effects left-to-right, so if both effects are the same, the
378 // second essentially overrides the first.
379 e1 = CMD_NONE;
380 } else if(!vol)
381 {
382 lostCommand |= (ModCommand::TwoRegularCommandsToMPT(e1, p1, e2, p2).first != CMD_NONE);
383 m.volcmd = e1;
384 m.vol = p1;
385 } else
386 {
387 if(ModCommand::GetEffectWeight((ModCommand::COMMAND)e1) > ModCommand::GetEffectWeight((ModCommand::COMMAND)e2))
388 {
389 std::swap(e1, e2);
390 std::swap(p1, p2);
391 }
392 }
393
394 m.command = e2;
395 m.param = p2;
396 return lostCommand;
397 }
398
399
MDLReadEnvelopes(FileReader file,std::vector<MDLEnvelope> & envelopes)400 static void MDLReadEnvelopes(FileReader file, std::vector<MDLEnvelope> &envelopes)
401 {
402 if(!file.CanRead(1))
403 return;
404
405 envelopes.resize(64);
406 uint8 numEnvs = file.ReadUint8();
407 while(numEnvs--)
408 {
409 MDLEnvelope mdlEnv;
410 if(!file.ReadStruct(mdlEnv) || mdlEnv.envNum > 63)
411 continue;
412 envelopes[mdlEnv.envNum] = mdlEnv;
413 }
414 }
415
416
CopyEnvelope(InstrumentEnvelope & mptEnv,uint8 flags,std::vector<MDLEnvelope> & envelopes)417 static void CopyEnvelope(InstrumentEnvelope &mptEnv, uint8 flags, std::vector<MDLEnvelope> &envelopes)
418 {
419 uint8 envNum = flags & 0x3F;
420 if(envNum < envelopes.size())
421 envelopes[envNum].ConvertToMPT(mptEnv);
422 mptEnv.dwFlags.set(ENV_ENABLED, (flags & 0x80) && !mptEnv.empty());
423 }
424
425
ValidateHeader(const MDLFileHeader & fileHeader)426 static bool ValidateHeader(const MDLFileHeader &fileHeader)
427 {
428 if(std::memcmp(fileHeader.id, "DMDL", 4)
429 || fileHeader.version >= 0x20)
430 {
431 return false;
432 }
433 return true;
434 }
435
436
ProbeFileHeaderMDL(MemoryFileReader file,const uint64 * pfilesize)437 CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderMDL(MemoryFileReader file, const uint64 *pfilesize)
438 {
439 MDLFileHeader fileHeader;
440 if(!file.ReadStruct(fileHeader))
441 {
442 return ProbeWantMoreData;
443 }
444 if(!ValidateHeader(fileHeader))
445 {
446 return ProbeFailure;
447 }
448 MPT_UNREFERENCED_PARAMETER(pfilesize);
449 return ProbeSuccess;
450 }
451
452
ReadMDL(FileReader & file,ModLoadingFlags loadFlags)453 bool CSoundFile::ReadMDL(FileReader &file, ModLoadingFlags loadFlags)
454 {
455 file.Rewind();
456 MDLFileHeader fileHeader;
457 if(!file.ReadStruct(fileHeader))
458 {
459 return false;
460 }
461 if(!ValidateHeader(fileHeader))
462 {
463 return false;
464 }
465 if(loadFlags == onlyVerifyHeader)
466 {
467 return true;
468 }
469
470 ChunkReader chunkFile(file);
471 ChunkReader::ChunkList<MDLChunk> chunks = chunkFile.ReadChunks<MDLChunk>(0);
472
473 // Read global info
474 FileReader chunk = chunks.GetChunk(MDLChunk::idInfo);
475 MDLInfoBlock info;
476 if(!chunk.IsValid() || !chunk.ReadStruct(info))
477 {
478 return false;
479 }
480
481 InitializeGlobals(MOD_TYPE_MDL);
482 m_SongFlags = SONG_ITCOMPATGXX;
483 m_playBehaviour.set(kPerChannelGlobalVolSlide);
484 m_playBehaviour.set(kApplyOffsetWithoutNote);
485 m_playBehaviour.reset(kITVibratoTremoloPanbrello);
486 m_playBehaviour.reset(kITSCxStopsSample); // Gate effect in underbeat.mdl
487
488 m_modFormat.formatName = U_("Digitrakker");
489 m_modFormat.type = U_("mdl");
490 m_modFormat.madeWithTracker = U_("Digitrakker ") + (
491 (fileHeader.version == 0x11) ? U_("3") // really could be 2.99b - close enough
492 : (fileHeader.version == 0x10) ? U_("2.3")
493 : (fileHeader.version == 0x00) ? U_("2.0 - 2.2b") // there was no 1.x release
494 : U_(""));
495 m_modFormat.charset = mpt::Charset::CP437;
496
497 m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, info.title);
498 m_songArtist = mpt::ToUnicode(mpt::Charset::CP437, mpt::String::ReadBuf(mpt::String::spacePadded, info.composer));
499
500 m_nDefaultGlobalVolume = info.globalVol + 1;
501 m_nDefaultSpeed = Clamp<uint8, uint8>(info.speed, 1, 255);
502 m_nDefaultTempo.Set(Clamp<uint8, uint8>(info.tempo, 4, 255));
503
504 ReadOrderFromFile<uint8>(Order(), chunk, info.numOrders);
505 Order().SetRestartPos(info.restartPos);
506
507 m_nChannels = 0;
508 for(CHANNELINDEX c = 0; c < 32; c++)
509 {
510 ChnSettings[c].Reset();
511 ChnSettings[c].nPan = (info.chnSetup[c] & 0x7F) * 2u;
512 if(ChnSettings[c].nPan == 254)
513 ChnSettings[c].nPan = 256;
514 if(info.chnSetup[c] & 0x80)
515 ChnSettings[c].dwFlags.set(CHN_MUTE);
516 else
517 m_nChannels = c + 1;
518 chunk.ReadString<mpt::String::spacePadded>(ChnSettings[c].szName, 8);
519 }
520
521 // Read song message
522 chunk = chunks.GetChunk(MDLChunk::idMessage);
523 m_songMessage.Read(chunk, chunk.GetLength(), SongMessage::leCR);
524
525 // Read sample info and data
526 chunk = chunks.GetChunk(MDLChunk::idSampleInfo);
527 if(chunk.IsValid())
528 {
529 FileReader dataChunk = chunks.GetChunk(MDLChunk::ifSampleData);
530
531 uint8 numSamples = chunk.ReadUint8();
532 for(uint8 smp = 0; smp < numSamples; smp++)
533 {
534 const SAMPLEINDEX sampleIndex = chunk.ReadUint8();
535 if(sampleIndex == 0 || sampleIndex >= MAX_SAMPLES || !chunk.CanRead(32 + 8 + 2 + 12 + 2))
536 break;
537
538 if(sampleIndex > GetNumSamples())
539 m_nSamples = sampleIndex;
540
541 ModSample &sample = Samples[sampleIndex];
542 sample.Initialize();
543 sample.Set16BitCuePoints();
544
545 chunk.ReadString<mpt::String::spacePadded>(m_szNames[sampleIndex], 32);
546 chunk.ReadString<mpt::String::spacePadded>(sample.filename, 8);
547
548 uint32 c4speed;
549 if(fileHeader.version < 0x10)
550 c4speed = chunk.ReadUint16LE();
551 else
552 c4speed = chunk.ReadUint32LE();
553 sample.nC5Speed = c4speed * 2u;
554 sample.nLength = chunk.ReadUint32LE();
555 sample.nLoopStart = chunk.ReadUint32LE();
556 sample.nLoopEnd = chunk.ReadUint32LE();
557 if(sample.nLoopEnd != 0)
558 {
559 sample.uFlags.set(CHN_LOOP);
560 sample.nLoopEnd += sample.nLoopStart;
561 }
562 uint8 volume = chunk.ReadUint8();
563 if(fileHeader.version < 0x10)
564 sample.nVolume = volume;
565 uint8 flags = chunk.ReadUint8();
566
567 if(flags & 0x01)
568 {
569 sample.uFlags.set(CHN_16BIT);
570 sample.nLength /= 2u;
571 sample.nLoopStart /= 2u;
572 sample.nLoopEnd /= 2u;
573 }
574
575 sample.uFlags.set(CHN_PINGPONGLOOP, (flags & 0x02) != 0);
576
577 SampleIO sampleIO(
578 (flags & 0x01) ? SampleIO::_16bit : SampleIO::_8bit,
579 SampleIO::mono,
580 SampleIO::littleEndian,
581 (flags & 0x0C) ? SampleIO::MDL : SampleIO::signedPCM);
582
583 if(loadFlags & loadSampleData)
584 {
585 sampleIO.ReadSample(sample, dataChunk);
586 }
587 }
588 }
589
590 chunk = chunks.GetChunk(MDLChunk::idInstrs);
591 if(chunk.IsValid())
592 {
593 std::vector<MDLEnvelope> volEnvs, panEnvs, pitchEnvs;
594 MDLReadEnvelopes(chunks.GetChunk(MDLChunk::idVolEnvs), volEnvs);
595 MDLReadEnvelopes(chunks.GetChunk(MDLChunk::idPanEnvs), panEnvs);
596 MDLReadEnvelopes(chunks.GetChunk(MDLChunk::idFreqEnvs), pitchEnvs);
597
598 uint8 numInstruments = chunk.ReadUint8();
599 for(uint8 i = 0; i < numInstruments; i++)
600 {
601 const auto [ins, numSamples] = chunk.ReadArray<uint8, 2>();
602 uint8 firstNote = 0;
603 ModInstrument *mptIns = nullptr;
604 if(ins == 0
605 || !chunk.CanRead(32 + sizeof(MDLSampleHeader) * numSamples)
606 || (mptIns = AllocateInstrument(ins)) == nullptr)
607 {
608 chunk.Skip(32 + sizeof(MDLSampleHeader) * numSamples);
609 continue;
610 }
611
612 chunk.ReadString<mpt::String::spacePadded>(mptIns->name, 32);
613 for(uint8 smp = 0; smp < numSamples; smp++)
614 {
615 MDLSampleHeader sampleHeader;
616 chunk.ReadStruct(sampleHeader);
617 if(sampleHeader.smpNum == 0 || sampleHeader.smpNum > GetNumSamples())
618 continue;
619
620 LimitMax(sampleHeader.lastNote, static_cast<uint8>(std::size(mptIns->Keyboard)));
621 for(uint8 n = firstNote; n <= sampleHeader.lastNote; n++)
622 {
623 mptIns->Keyboard[n] = sampleHeader.smpNum;
624 }
625 firstNote = sampleHeader.lastNote + 1;
626
627 CopyEnvelope(mptIns->VolEnv, sampleHeader.volEnvFlags, volEnvs);
628 CopyEnvelope(mptIns->PanEnv, sampleHeader.panEnvFlags, panEnvs);
629 CopyEnvelope(mptIns->PitchEnv, sampleHeader.freqEnvFlags, pitchEnvs);
630 mptIns->nFadeOut = (sampleHeader.fadeout + 1u) / 2u;
631 #ifdef MODPLUG_TRACKER
632 if((mptIns->VolEnv.dwFlags & (ENV_ENABLED | ENV_LOOP)) == ENV_ENABLED)
633 {
634 // Fade-out is only supposed to happen on key-off, not at the end of a volume envelope.
635 // Fake it by putting a loop at the end.
636 mptIns->VolEnv.nLoopStart = mptIns->VolEnv.nLoopEnd = static_cast<uint8>(mptIns->VolEnv.size() - 1);
637 mptIns->VolEnv.dwFlags.set(ENV_LOOP);
638 }
639 for(auto &p : mptIns->PitchEnv)
640 {
641 // Scale pitch envelope
642 p.value = (p.value * 6u) / 16u;
643 }
644 #endif // MODPLUG_TRACKER
645
646 // Samples were already initialized above. Let's hope they are not going to be re-used with different volume / panning / vibrato...
647 ModSample &mptSmp = Samples[sampleHeader.smpNum];
648
649 // This flag literally enables and disables the default volume of a sample. If you disable this flag,
650 // the sample volume of a previously sample is re-used, even if you put an instrument number next to the note.
651 if(sampleHeader.volEnvFlags & 0x40)
652 mptSmp.nVolume = sampleHeader.volume;
653 else
654 mptSmp.uFlags.set(SMP_NODEFAULTVOLUME);
655 mptSmp.nPan = std::min(static_cast<uint16>(sampleHeader.panning * 2), uint16(254));
656 mptSmp.nVibType = MDLVibratoType[sampleHeader.vibType & 3];
657 mptSmp.nVibSweep = sampleHeader.vibSweep;
658 mptSmp.nVibDepth = (sampleHeader.vibDepth + 3u) / 4u;
659 mptSmp.nVibRate = sampleHeader.vibSpeed;
660 // Convert to IT-like vibrato sweep
661 if(mptSmp.nVibSweep != 0)
662 mptSmp.nVibSweep = mpt::saturate_cast<decltype(mptSmp.nVibSweep)>(Util::muldivr_unsigned(mptSmp.nVibDepth, 256, mptSmp.nVibSweep));
663 else
664 mptSmp.nVibSweep = 255;
665 if(sampleHeader.panEnvFlags & 0x40)
666 mptSmp.uFlags.set(CHN_PANNING);
667 }
668 }
669 }
670
671 // Read pattern tracks
672 std::vector<FileReader> tracks;
673 if((loadFlags & loadPatternData) && (chunk = chunks.GetChunk(MDLChunk::idTracks)).IsValid())
674 {
675 uint32 numTracks = chunk.ReadUint16LE();
676 tracks.resize(numTracks + 1);
677 for(uint32 i = 1; i <= numTracks; i++)
678 {
679 tracks[i] = chunk.ReadChunk(chunk.ReadUint16LE());
680 }
681 }
682
683 // Read actual patterns
684 if((loadFlags & loadPatternData) && (chunk = chunks.GetChunk(MDLChunk::idPats)).IsValid())
685 {
686 PATTERNINDEX numPats = chunk.ReadUint8();
687
688 // In case any muted channels contain data, be sure that we import them as well.
689 for(PATTERNINDEX pat = 0; pat < numPats; pat++)
690 {
691 CHANNELINDEX numChans = 32;
692 if(fileHeader.version >= 0x10)
693 {
694 MDLPatternHeader patHead;
695 chunk.ReadStruct(patHead);
696 if(patHead.channels > m_nChannels && patHead.channels <= 32)
697 m_nChannels = patHead.channels;
698 numChans = patHead.channels;
699 }
700 for(CHANNELINDEX chn = 0; chn < numChans; chn++)
701 {
702 if(chunk.ReadUint16LE() > 0 && chn >= m_nChannels && chn < 32)
703 m_nChannels = chn + 1;
704 }
705 }
706 chunk.Seek(1);
707
708 Patterns.ResizeArray(numPats);
709 for(PATTERNINDEX pat = 0; pat < numPats; pat++)
710 {
711 CHANNELINDEX numChans = 32;
712 ROWINDEX numRows = 64;
713 std::string name;
714 if(fileHeader.version >= 0x10)
715 {
716 MDLPatternHeader patHead;
717 chunk.ReadStruct(patHead);
718 numChans = patHead.channels;
719 numRows = patHead.lastRow + 1;
720 name = mpt::String::ReadBuf(mpt::String::spacePadded, patHead.name);
721 }
722
723 if(!Patterns.Insert(pat, numRows))
724 {
725 chunk.Skip(2 * numChans);
726 continue;
727 }
728 Patterns[pat].SetName(name);
729
730 for(CHANNELINDEX chn = 0; chn < numChans; chn++)
731 {
732 uint16 trkNum = chunk.ReadUint16LE();
733 if(!trkNum || trkNum >= tracks.size() || chn >= m_nChannels)
734 continue;
735
736 FileReader &track = tracks[trkNum];
737 track.Rewind();
738 ROWINDEX row = 0;
739 while(row < numRows && track.CanRead(1))
740 {
741 ModCommand *m = Patterns[pat].GetpModCommand(row, chn);
742 uint8 b = track.ReadUint8();
743 uint8 x = (b >> 2), y = (b & 3);
744 switch(y)
745 {
746 case 0:
747 // (x + 1) empty notes follow
748 row += x + 1;
749 break;
750 case 1:
751 // Repeat previous note (x + 1) times
752 if(row > 0)
753 {
754 ModCommand &orig = *Patterns[pat].GetpModCommand(row - 1, chn);
755 do
756 {
757 *m = orig;
758 m += m_nChannels;
759 row++;
760 } while (row < numRows && x--);
761 }
762 break;
763 case 2:
764 // Copy note from row x
765 if(row > x)
766 {
767 *m = *Patterns[pat].GetpModCommand(x, chn);
768 }
769 row++;
770 break;
771 case 3:
772 // New note data
773 if(x & MDLNOTE_NOTE)
774 {
775 b = track.ReadUint8();
776 m->note = (b > 120) ? static_cast<ModCommand::NOTE>(NOTE_KEYOFF) : static_cast<ModCommand::NOTE>(b);
777 }
778 if(x & MDLNOTE_SAMPLE)
779 {
780 m->instr = track.ReadUint8();
781 }
782 {
783 uint8 vol = 0, e1 = 0, e2 = 0, p1 = 0, p2 = 0;
784 if(x & MDLNOTE_VOLUME)
785 {
786 vol = track.ReadUint8();
787 }
788 if(x & MDLNOTE_EFFECTS)
789 {
790 b = track.ReadUint8();
791 e1 = (b & 0x0F);
792 e2 = (b >> 4);
793 }
794 if(x & MDLNOTE_PARAM1)
795 p1 = track.ReadUint8();
796 if(x & MDLNOTE_PARAM2)
797 p2 = track.ReadUint8();
798 ImportMDLCommands(*m, vol, e1, e2, p1, p2);
799 }
800
801 row++;
802 break;
803 }
804 }
805 }
806 }
807 }
808
809 if((loadFlags & loadPatternData) && (chunk = chunks.GetChunk(MDLChunk::idPatNames)).IsValid())
810 {
811 PATTERNINDEX i = 0;
812 while(i < Patterns.Size() && chunk.CanRead(16))
813 {
814 char name[17];
815 chunk.ReadString<mpt::String::spacePadded>(name, 16);
816 Patterns[i].SetName(name);
817 }
818 }
819
820 return true;
821 }
822
823
824 OPENMPT_NAMESPACE_END
825