1 /*
2 * Load_med.cpp
3 * ------------
4 * Purpose: OctaMED / MED Soundstudio module loader
5 * Notes : Support for synthesized instruments is still missing.
6 * Authors: OpenMPT Devs
7 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
8 */
9
10 #include "stdafx.h"
11 #include "Loaders.h"
12
13 #ifdef MPT_WITH_VST
14 #include "../mptrack/Vstplug.h"
15 #include "plugins/PluginManager.h"
16 #endif // MPT_WITH_VST
17 #include "mpt/io/base.hpp"
18 #include "mpt/io/io.hpp"
19 #include "mpt/io/io_span.hpp"
20 #include "mpt/io/io_stdstream.hpp"
21
22 #include <map>
23
24 OPENMPT_NAMESPACE_BEGIN
25
26 struct MMD0FileHeader
27 {
28 char mmd[3]; // "MMD" for the first song in file, "MCN" for the rest
29 uint8be version; // '0'-'3'
30 uint32be modLength; // Size of file
31 uint32be songOffset; // Position in file for the first song
32 uint16be playerSettings1[2]; // Internal variables for the play routine
33 uint32be blockArrOffset; // Position in file for blocks (patterns)
34 uint8be flags;
35 uint8be reserved1[3];
36 uint32be sampleArrOffset; // Position in file for samples (should be identical between songs)
37 uint32be reserved2;
38 uint32be expDataOffset; // Absolute offset in file for ExpData (0 if not present)
39 uint32be reserved3;
40 char playerSettings2[11]; // Internal variables for the play routine
41 uint8be extraSongs; // Number of songs - 1
42 };
43
44 MPT_BINARY_STRUCT(MMD0FileHeader, 52)
45
46
47 struct MMD0Sample
48 {
49 uint16be loopStart;
50 uint16be loopLength;
51 uint8be midiChannel;
52 uint8be midiPreset;
53 uint8be sampleVolume;
54 int8be sampleTranspose;
55 };
56
57 MPT_BINARY_STRUCT(MMD0Sample, 8)
58
59
60 // Song header for MMD0/MMD1
61 struct MMD0Song
62 {
63 uint8be sequence[256];
64 };
65
66 MPT_BINARY_STRUCT(MMD0Song, 256)
67
68
69 // Song header for MMD2/MMD3
70 struct MMD2Song
71 {
72 enum Flags3
73 {
74 FLAG3_STEREO = 0x01, // Mixing in stereo
75 FLAG3_FREEPAN = 0x02, // Mixing flag: free pan
76 };
77
78 uint32be playSeqTableOffset;
79 uint32be sectionTableOffset;
80 uint32be trackVolsOffset;
81 uint16be numTracks;
82 uint16be numPlaySeqs;
83 uint32be trackPanOffset; // 0: all centered (according to docs, MED Soundstudio uses Amiga hard-panning instead)
84 uint32be flags3;
85 uint16be volAdjust; // Volume adjust (%)
86 uint16be mixChannels; // Mixing channels, 0 means 4
87 uint8be mixEchoType; // 0 = nothing, 1 = normal, 2 = cross
88 uint8be mixEchoDepth; // 1 - 6, 0 = default
89 uint16be mixEchoLength; // Echo length in milliseconds
90 int8be mixStereoSep; // Stereo separation
91 char pad0[223];
92 };
93
94 MPT_BINARY_STRUCT(MMD2Song, 256)
95
96
97 // Common song header
98 struct MMDSong
99 {
100 enum Flags
101 {
102 FLAG_FILTERON = 0x01, // The hardware audio filter is on
103 FLAG_JUMPINGON = 0x02, // Mouse pointer jumping on
104 FLAG_JUMP8TH = 0x04, // ump every 8th line (not in OctaMED Pro)
105 FLAG_INSTRSATT = 0x08, // sng+samples indicator (not useful in MMDs)
106 FLAG_VOLHEX = 0x10, // volumes are HEX
107 FLAG_STSLIDE = 0x20, // use ST/NT/PT compatible sliding
108 FLAG_8CHANNEL = 0x40, // this is OctaMED 5-8 channel song
109 FLAG_SLOWHQ = 0x80, // HQ V2-4 compatibility mode
110 };
111
112 enum Flags2
113 {
114 FLAG2_BMASK = 0x1F, // (bits 0-4) BPM beat length (in lines)
115 FLAG2_BPM = 0x20, // BPM mode on
116 FLAG2_MIX = 0x80, // Module uses mixing
117 };
118
119 uint16be numBlocks; // Number of blocks in current song
120 uint16be songLength; // MMD0: Number of sequence numbers in the play sequence list, MMD2: Number of sections
121 char song[256];
GetMMD0SongMMDSong122 MMD0Song GetMMD0Song() const
123 {
124 static_assert(sizeof(MMD0Song) == sizeof(song));
125 return mpt::bit_cast<MMD0Song>(song);
126 }
GetMMD2SongMMDSong127 MMD2Song GetMMD2Song() const
128 {
129 static_assert(sizeof(MMD2Song) == sizeof(song));
130 return mpt::bit_cast<MMD2Song>(song);
131 }
132 uint16be defaultTempo;
133 int8be playTranspose; // The global play transpose value for current song
134 uint8be flags;
135 uint8be flags2;
136 uint8be tempo2; // Timing pulses per line (ticks)
137 uint8be trackVol[16]; // 1...64 in MMD0/MMD1, reserved in MMD2
138 uint8be masterVol; // 1...64
139 uint8be numSamples;
140 };
141
142 MPT_BINARY_STRUCT(MMDSong, 284)
143
144
145 struct MMD2PlaySeq
146 {
147 char name[32];
148 uint32be commandTableOffset;
149 uint32be reserved;
150 uint16be length; // Number of entries
151 };
152
153 MPT_BINARY_STRUCT(MMD2PlaySeq, 42)
154
155
156 struct MMD0PatternHeader
157 {
158 uint8be numTracks;
159 uint8be numRows;
160 };
161
162 MPT_BINARY_STRUCT(MMD0PatternHeader, 2)
163
164
165 struct MMD1PatternHeader
166 {
167 uint16be numTracks;
168 uint16be numRows;
169 uint32be blockInfoOffset;
170 };
171
172 MPT_BINARY_STRUCT(MMD1PatternHeader, 8)
173
174
175 struct MMDPlaySeqCommand
176 {
177 enum Command
178 {
179 kStop = 1,
180 kJump = 2,
181 };
182
183 uint16be offset; // Offset within current play sequence, 0xFFFF = end of list
184 uint8be command; // Stop = 1, Jump = 2
185 uint8be extraSize;
186 };
187
188 MPT_BINARY_STRUCT(MMDPlaySeqCommand, 4)
189
190
191 struct MMDBlockInfo
192 {
193 uint32be highlightMaskOffset;
194 uint32be nameOffset;
195 uint32be nameLength;
196 uint32be pageTableOffset; // File offset of command page table
197 uint32be cmdExtTableOffset; // File offset of command extension table (second parameter)
198 uint32be reserved[4];
199 };
200
201 MPT_BINARY_STRUCT(MMDBlockInfo, 36)
202
203
204 struct MMDInstrHeader
205 {
206 enum Types
207 {
208 VSTI = -4,
209 HIGHLIFE = -3,
210 HYBRID = -2,
211 SYNTHETIC = -1,
212 SAMPLE = 0, // an ordinary 1-octave sample (or MIDI)
213 IFF5OCT = 1, // 5 octaves
214 IFF3OCT = 2, // 3 octaves
215 // The following ones are recognized by OctaMED Pro only
216 IFF2OCT = 3, // 2 octaves
217 IFF4OCT = 4, // 4 octaves
218 IFF6OCT = 5, // 6 octaves
219 IFF7OCT = 6, // 7 octaves
220 // OctaMED Pro V5 + later
221 EXTSAMPLE = 7, // two extra-low octaves
222
223 TYPEMASK = 0x0F,
224
225 S_16 = 0x10,
226 STEREO = 0x20,
227 DELTA = 0x40,
228 PACKED = 0x80, // MMDPackedSampleHeader follows
229 OBSOLETE_MD16 = 0x18,
230 };
231
232 uint32be length;
233 int16be type;
234 };
235
236 MPT_BINARY_STRUCT(MMDInstrHeader, 6)
237
238
239 struct MMDPackedSampleHeader
240 {
241 uint16be packType; // Only 1 = ADPCM is supported
242 uint16be subType; // Packing subtype
243 // ADPCM subtype
244 // 1: g723_40
245 // 2: g721
246 // 3: g723_24
247 uint8be commonFlags; // flags common to all packtypes (none defined so far)
248 uint8be packerFlags; // flags for the specific packtype
249 uint32be leftChLen; // packed length of left channel in bytes
250 uint32be rightChLen; // packed length of right channel in bytes (ONLY PRESENT IN STEREO SAMPLES)
251 };
252
253 MPT_BINARY_STRUCT(MMDPackedSampleHeader, 14)
254
255
256 struct MMDInstrExt
257 {
258 enum
259 {
260 SSFLG_LOOP = 0x01, // Loop On / Off
261 SSFLG_EXTPSET = 0x02, // Ext.Preset
262 SSFLG_DISABLED = 0x04, // Disabled
263 SSFLG_PINGPONG = 0x08, // Ping-pong looping
264 };
265
266 uint8be hold; // 0...127
267 uint8be decay; // 0...127
268 uint8be suppressMidiOff;
269 int8be finetune;
270 // Below fields saved by >= V5
271 uint8be defaultPitch;
272 uint8be instrFlags;
273 uint16be longMidiPreset; // Legacy MIDI program mode that doesn't use banks but a combination of two program change commands
274 // Below fields saved by >= V5.02
275 uint8be outputDevice;
276 uint8be reserved;
277 // Below fields saved by >= V7
278 uint32be loopStart;
279 uint32be loopLength;
280 // Not sure which version starts saving those but they are saved by MED Soundstudio for Windows
281 uint8 volume; // 0...127
282 uint8 outputPort; // Index into user-configurable device list (NOT WinAPI port index)
283 uint16le midiBank;
284 };
285
286 MPT_BINARY_STRUCT(MMDInstrExt, 22)
287
288
289 struct MMDInstrInfo
290 {
291 char name[40];
292 };
293
294 MPT_BINARY_STRUCT(MMDInstrInfo, 40)
295
296
297 struct MMD0Exp
298 {
299 uint32be nextModOffset;
300 uint32be instrExtOffset;
301 uint16be instrExtEntries;
302 uint16be instrExtEntrySize;
303 uint32be annoText;
304 uint32be annoLength;
305 uint32be instrInfoOffset;
306 uint16be instrInfoEntries;
307 uint16be instrInfoEntrySize;
308 uint32be jumpMask;
309 uint32be rgbTable;
310 uint8be channelSplit[4];
311 uint32be notationInfoOffset;
312 uint32be songNameOffset;
313 uint32be songNameLength;
314 uint32be midiDumpOffset;
315 uint32be mmdInfoOffset;
316 uint32be arexxOffset;
317 uint32be midiCmd3xOffset;
318 uint32be trackInfoOffset; // Pointer to song->numtracks pointers to tag lists
319 uint32be effectInfoOffset; // Pointers to group pointers
320 uint32be tagEnd;
321 };
322
323 MPT_BINARY_STRUCT(MMD0Exp, 80)
324
325
326 struct MMDTag
327 {
328 enum TagType
329 {
330 // Generic MMD tags
331 MMDTAG_END = 0x00000000,
332 MMDTAG_PTR = 0x80000000, // Data needs relocation
333 MMDTAG_MUSTKNOW = 0x40000000, // Loader must fail if this isn't recognized
334 MMDTAG_MUSTWARN = 0x20000000, // Loader must warn if this isn't recognized
335 MMDTAG_MASK = 0x1FFFFFFF,
336
337 // ExpData tags
338 // # of effect groups, including the global group (will override settings in MMDSong struct), default = 1
339 MMDTAG_EXP_NUMFXGROUPS = 1,
340 MMDTAG_TRK_FXGROUP = 3,
341
342 MMDTAG_TRK_NAME = 1, // trackinfo tags
343 MMDTAG_TRK_NAMELEN = 2, // namelen includes zero term.
344
345 // effectinfo tags
346 MMDTAG_FX_ECHOTYPE = 1,
347 MMDTAG_FX_ECHOLEN = 2,
348 MMDTAG_FX_ECHODEPTH = 3,
349 MMDTAG_FX_STEREOSEP = 4,
350 MMDTAG_FX_GROUPNAME = 5, // the Global Effects group shouldn't have name saved!
351 MMDTAG_FX_GRPNAMELEN = 6, // namelen includes zero term.
352 };
353
354 uint32be type;
355 uint32be data;
356 };
357
358 MPT_BINARY_STRUCT(MMDTag, 8)
359
360
361 struct MMDDump
362 {
363 uint32be length;
364 uint32be dataPointer;
365 uint16be extLength; // If >= 20: name follows as char[20]
366 };
367
368 MPT_BINARY_STRUCT(MMDDump, 10)
369
370
MMDTempoToBPM(uint32 tempo,bool is8Ch,bool bpmMode,uint8 rowsPerBeat)371 static TEMPO MMDTempoToBPM(uint32 tempo, bool is8Ch, bool bpmMode, uint8 rowsPerBeat)
372 {
373 if(bpmMode && !is8Ch)
374 {
375 // You would have thought that we could use modern tempo mode here.
376 // Alas, the number of ticks per row still influences the tempo. :(
377 return TEMPO((tempo * rowsPerBeat) / 4.0);
378 }
379 if(is8Ch && tempo > 0)
380 {
381 LimitMax(tempo, 10u);
382 // MED Soundstudio uses these tempos when importing old files
383 static constexpr uint8 tempos[10] = {179, 164, 152, 141, 131, 123, 116, 110, 104, 99};
384 return TEMPO(tempos[tempo - 1], 0);
385 } else if(tempo > 0 && tempo <= 10)
386 {
387 // SoundTracker compatible tempo
388 return TEMPO((6.0 * 1773447.0 / 14500.0) / tempo);
389 }
390
391 return TEMPO(tempo / 0.264);
392 }
393
394
ConvertMEDEffect(ModCommand & m,bool is8ch,bool bpmMode,uint8 rowsPerBeat,bool volHex)395 static void ConvertMEDEffect(ModCommand &m, bool is8ch, bool bpmMode, uint8 rowsPerBeat, bool volHex)
396 {
397 switch(m.command)
398 {
399 case 0x04: // Vibrato (twice as deep as in ProTracker)
400 m.command = CMD_VIBRATO;
401 m.param = (std::min<uint8>(m.param >> 3, 0x0F) << 4) | std::min<uint8>((m.param & 0x0F) * 2, 0x0F);
402 break;
403 case 0x08: // Hold and decay
404 m.command = CMD_NONE;
405 break;
406 case 0x09: // Set secondary speed
407 if(m.param > 0 && m.param <= 20)
408 m.command = CMD_SPEED;
409 else
410 m.command = CMD_NONE;
411 break;
412 case 0x0C: // Set Volume
413 m.command = CMD_VOLUME;
414 if(!volHex && m.param < 0x99)
415 m.param = (m.param >> 4) * 10 + (m.param & 0x0F);
416 else if(volHex)
417 m.param = ((m.param & 0x7F) + 1) / 2;
418 else
419 m.command = CMD_NONE;
420 break;
421 case 0x0D:
422 m.command = CMD_VOLUMESLIDE;
423 break;
424 case 0x0E: // Synth jump
425 m.command = CMD_NONE;
426 break;
427 case 0x0F: // Misc
428 if(m.param == 0)
429 {
430 m.command = CMD_PATTERNBREAK;
431 } else if(m.param <= 0xF0)
432 {
433 m.command = CMD_TEMPO;
434 if(m.param < 0x03) // This appears to be a bug in OctaMED which is not emulated in MED Soundstudio on Windows.
435 m.param = 0x70;
436 else
437 m.param = mpt::saturate_round<ModCommand::PARAM>(MMDTempoToBPM(m.param, is8ch, bpmMode, rowsPerBeat).ToDouble());
438 #ifdef MODPLUG_TRACKER
439 if(m.param < 0x20)
440 m.param = 0x20;
441 #endif // MODPLUG_TRACKER
442 } else switch(m.command)
443 {
444 case 0xF1: // Play note twice
445 m.command = CMD_MODCMDEX;
446 m.param = 0x93;
447 break;
448 case 0xF2: // Delay note
449 m.command = CMD_MODCMDEX;
450 m.param = 0xD3;
451 break;
452 case 0xF3: // Play note three times
453 m.command = CMD_MODCMDEX;
454 m.param = 0x92;
455 break;
456 case 0xF8: // Turn filter off
457 case 0xF9: // Turn filter on
458 m.command = CMD_MODCMDEX;
459 m.param = 0xF9 - m.param;
460 break;
461 case 0xFA: // MIDI pedal on
462 case 0xFB: // MIDI pedal off
463 case 0xFD: // Set pitch
464 case 0xFE: // End of song
465 m.command = CMD_NONE;
466 break;
467 case 0xFF: // Turn note off
468 m.note = NOTE_NOTECUT;
469 m.command = CMD_NONE;
470 break;
471 default:
472 m.command = CMD_NONE;
473 break;
474 }
475 break;
476 case 0x10: // MIDI message
477 m.command = CMD_MIDI;
478 m.param |= 0x80;
479 break;
480 case 0x11: // Slide pitch up
481 m.command = CMD_MODCMDEX;
482 m.param = 0x10 | std::min<uint8>(m.param, 0x0F);
483 break;
484 case 0x12: // Slide pitch down
485 m.command = CMD_MODCMDEX;
486 m.param = 0x20 | std::min<uint8>(m.param, 0x0F);
487 break;
488 case 0x14: // Vibrato (ProTracker compatible depth, but faster)
489 m.command = CMD_VIBRATO;
490 m.param = (std::min<uint8>((m.param >> 4) + 1, 0x0F) << 4) | (m.param & 0x0F);
491 break;
492 case 0x15: // Set finetune
493 m.command = CMD_MODCMDEX;
494 m.param = 0x50 | (m.param & 0x0F);
495 break;
496 case 0x16: // Loop
497 m.command = CMD_MODCMDEX;
498 m.param = 0x60 | std::min<uint8>(m.param, 0x0F);
499 break;
500 case 0x18: // Stop note
501 m.command = CMD_MODCMDEX;
502 m.param = 0xC0 | std::min<uint8>(m.param, 0x0F);
503 break;
504 case 0x19: // Sample Offset
505 m.command = CMD_OFFSET;
506 break;
507 case 0x1A: // Slide volume up once
508 m.command = CMD_MODCMDEX;
509 m.param = 0xA0 | std::min<uint8>(m.param, 0x0F);
510 break;
511 case 0x1B: // Slide volume down once
512 m.command = CMD_MODCMDEX;
513 m.param = 0xB0 | std::min<uint8>(m.param, 0x0F);
514 break;
515 case 0x1C: // MIDI program
516 if(m.param > 0 && m.param <= 128)
517 {
518 m.command = CMD_MIDI;
519 m.param--;
520 } else
521 {
522 m.command = CMD_NONE;
523 }
524 break;
525 case 0x1D: // Pattern break (in hex)
526 m.command = CMD_PATTERNBREAK;
527 break;
528 case 0x1E: // Repeat row
529 m.command = CMD_MODCMDEX;
530 m.param = 0xE0 | std::min<uint8>(m.param, 0x0F);
531 break;
532 case 0x1F: // Note delay and retrigger
533 {
534 if(m.param & 0xF0)
535 {
536 m.command = CMD_MODCMDEX;
537 m.param = 0xD0 | (m.param >> 4);
538 } else if(m.param & 0x0F)
539 {
540 m.command = CMD_MODCMDEX;
541 m.param = 0x90 | m.param;
542 } else
543 {
544 m.command = CMD_NONE;
545 }
546 break;
547 }
548 case 0x20: // Reverse sample + skip samples
549 if(m.param == 0 && m.vol == 0)
550 {
551 m.command = CMD_S3MCMDEX;
552 m.param = 0x9F;
553 } else
554 {
555 // Skip given number of samples
556 m.command = CMD_NONE;
557 }
558 break;
559 case 0x29: // Relative sample offset
560 if(m.vol > 0)
561 {
562 m.command = CMD_OFFSETPERCENTAGE;
563 m.param = mpt::saturate_cast<ModCommand::PARAM>(Util::muldiv_unsigned(m.param, 0x100, m.vol));
564 } else
565 {
566 m.command = CMD_NONE;
567 }
568 break;
569 case 0x2E: // Set panning
570 if(m.param <= 0x10 || m.param >= 0xF0)
571 {
572 m.command = CMD_PANNING8;
573 m.param = mpt::saturate_cast<ModCommand::PARAM>(((m.param ^ 0x80) - 0x70) * 8);
574 } else
575 {
576 m.command = CMD_NONE;
577 }
578 break;
579 default:
580 if(m.command < 0x10)
581 CSoundFile::ConvertModCommand(m);
582 else
583 m.command = CMD_NONE;
584 break;
585 }
586 }
587
588 #ifdef MPT_WITH_VST
ReadMEDStringUTF16BE(FileReader & file)589 static std::wstring ReadMEDStringUTF16BE(FileReader &file)
590 {
591 FileReader chunk = file.ReadChunk(file.ReadUint32BE());
592 std::wstring s(chunk.GetLength() / 2u, L'\0');
593 for(auto &c : s)
594 {
595 c = chunk.ReadUint16BE();
596 }
597 return s;
598 }
599 #endif // MPT_WITH_VST
600
601
MEDReadNextSong(FileReader & file,MMD0FileHeader & fileHeader,MMD0Exp & expData,MMDSong & songHeader)602 static void MEDReadNextSong(FileReader &file, MMD0FileHeader &fileHeader, MMD0Exp &expData, MMDSong &songHeader)
603 {
604 file.ReadStruct(fileHeader);
605 file.Seek(fileHeader.songOffset + 63 * sizeof(MMD0Sample));
606 file.ReadStruct(songHeader);
607 if(fileHeader.expDataOffset && file.Seek(fileHeader.expDataOffset))
608 file.ReadStruct(expData);
609 else
610 expData = {};
611 }
612
613
MEDScanNumChannels(FileReader & file,const uint8 version)614 static std::pair<CHANNELINDEX, SEQUENCEINDEX> MEDScanNumChannels(FileReader &file, const uint8 version)
615 {
616 MMD0FileHeader fileHeader;
617 MMD0Exp expData;
618 MMDSong songHeader;
619
620 file.Rewind();
621 uint32 songOffset = 0;
622 MEDReadNextSong(file, fileHeader, expData, songHeader);
623
624 SEQUENCEINDEX numSongs = std::min(MAX_SEQUENCES, mpt::saturate_cast<SEQUENCEINDEX>(fileHeader.expDataOffset ? fileHeader.extraSongs + 1 : 1));
625 CHANNELINDEX numChannels = 4;
626 // Scan patterns for max number of channels
627 for(SEQUENCEINDEX song = 0; song < numSongs; song++)
628 {
629 const PATTERNINDEX numPatterns = songHeader.numBlocks;
630 if(songHeader.numSamples > 63 || numPatterns > 0x7FFF)
631 return {};
632
633 for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
634 {
635 if(!file.Seek(fileHeader.blockArrOffset + pat * 4u)
636 || !file.Seek(file.ReadUint32BE()))
637 {
638 continue;
639 }
640 numChannels = std::max(numChannels, static_cast<CHANNELINDEX>(version < 1 ? file.ReadUint8() : file.ReadUint16BE()));
641 }
642
643 // If song offsets are going backwards, reject the file
644 if(expData.nextModOffset <= songOffset || !file.Seek(expData.nextModOffset))
645 {
646 numSongs = song + 1;
647 break;
648 }
649 songOffset = expData.nextModOffset;
650 MEDReadNextSong(file, fileHeader, expData, songHeader);
651 }
652 return {numChannels, numSongs};
653 }
654
655
ValidateHeader(const MMD0FileHeader & fileHeader)656 static bool ValidateHeader(const MMD0FileHeader &fileHeader)
657 {
658 if(std::memcmp(fileHeader.mmd, "MMD", 3)
659 || fileHeader.version < '0' || fileHeader.version > '3'
660 || fileHeader.songOffset < sizeof(MMD0FileHeader)
661 || fileHeader.songOffset > uint32_max - 63 * sizeof(MMD0Sample) - sizeof(MMDSong)
662 || fileHeader.blockArrOffset < sizeof(MMD0FileHeader)
663 || (fileHeader.sampleArrOffset > 0 && fileHeader.sampleArrOffset < sizeof(MMD0FileHeader))
664 || fileHeader.expDataOffset > uint32_max - sizeof(MMD0Exp))
665 {
666 return false;
667 }
668 return true;
669 }
670
671
GetHeaderMinimumAdditionalSize(const MMD0FileHeader & fileHeader)672 static uint64 GetHeaderMinimumAdditionalSize(const MMD0FileHeader &fileHeader)
673 {
674 return std::max<uint64>({ fileHeader.songOffset + 63 * sizeof(MMD0Sample) + sizeof(MMDSong),
675 fileHeader.blockArrOffset,
676 fileHeader.sampleArrOffset ? fileHeader.sampleArrOffset : sizeof(MMD0FileHeader),
677 fileHeader.expDataOffset + sizeof(MMD0Exp) }) - sizeof(MMD0FileHeader);
678 }
679
680
ProbeFileHeaderMED(MemoryFileReader file,const uint64 * pfilesize)681 CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderMED(MemoryFileReader file, const uint64 *pfilesize)
682 {
683 MMD0FileHeader fileHeader;
684 if(!file.ReadStruct(fileHeader))
685 return ProbeWantMoreData;
686 if(!ValidateHeader(fileHeader))
687 return ProbeFailure;
688 return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
689 }
690
691
ReadMED(FileReader & file,ModLoadingFlags loadFlags)692 bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
693 {
694 file.Rewind();
695 MMD0FileHeader fileHeader;
696 if(!file.ReadStruct(fileHeader))
697 return false;
698 if(!ValidateHeader(fileHeader))
699 return false;
700 if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
701 return false;
702 if(loadFlags == onlyVerifyHeader)
703 return true;
704
705 InitializeGlobals(MOD_TYPE_MED);
706 InitializeChannels();
707 const uint8 version = fileHeader.version - '0';
708
709 file.Seek(fileHeader.songOffset);
710 FileReader sampleHeaderChunk = file.ReadChunk(63 * sizeof(MMD0Sample));
711
712 MMDSong songHeader;
713 file.ReadStruct(songHeader);
714
715 if(songHeader.numSamples > 63 || songHeader.numBlocks > 0x7FFF)
716 return false;
717
718 MMD0Exp expData{};
719 if(fileHeader.expDataOffset && file.Seek(fileHeader.expDataOffset))
720 {
721 file.ReadStruct(expData);
722 }
723
724 const auto [numChannels, numSongs] = MEDScanNumChannels(file, version);
725 if(numChannels < 1 || numChannels > MAX_BASECHANNELS)
726 return false;
727 m_nChannels = numChannels;
728
729 // Start with the instruments, as those are shared between songs
730
731 std::vector<uint32be> instrOffsets;
732 if(fileHeader.sampleArrOffset)
733 {
734 file.Seek(fileHeader.sampleArrOffset);
735 file.ReadVector(instrOffsets, songHeader.numSamples);
736 } else if(songHeader.numSamples > 0)
737 {
738 return false;
739 }
740 m_nInstruments = m_nSamples = songHeader.numSamples;
741
742 // In MMD0 / MMD1, octave wrapping is not done for synth instruments
743 // - It's required e.g. for automatic terminated to.mmd0 and you got to let the music.mmd1
744 // - starkelsesirap.mmd0 (synth instruments) on the other hand don't need it
745 // In MMD2 / MMD3, the mix flag is used instead.
746 const bool hardwareMixSamples = (version < 2) || (version >= 2 && !(songHeader.flags2 & MMDSong::FLAG2_MIX));
747
748 bool needInstruments = false;
749 bool anySynthInstrs = false;
750 #ifdef MPT_WITH_VST
751 PLUGINDEX numPlugins = 0;
752 #endif // MPT_WITH_VST
753 for(SAMPLEINDEX ins = 1, smp = 1; ins <= m_nInstruments; ins++)
754 {
755 if(!AllocateInstrument(ins, smp))
756 return false;
757 ModInstrument &instr = *Instruments[ins];
758
759 MMDInstrHeader instrHeader{};
760 FileReader sampleChunk;
761 if(instrOffsets[ins - 1] != 0 && file.Seek(instrOffsets[ins - 1]))
762 {
763 file.ReadStruct(instrHeader);
764 sampleChunk = file.ReadChunk(instrHeader.length);
765 }
766 const bool isSynth = instrHeader.type < 0;
767 const size_t maskedType = static_cast<size_t>(instrHeader.type & MMDInstrHeader::TYPEMASK);
768
769 #ifdef MPT_WITH_VST
770 if(instrHeader.type == MMDInstrHeader::VSTI)
771 {
772 needInstruments = true;
773 sampleChunk.Skip(6); // 00 00 <size of following data>
774 const std::wstring type = ReadMEDStringUTF16BE(sampleChunk);
775 const std::wstring name = ReadMEDStringUTF16BE(sampleChunk);
776 if(type == L"VST")
777 {
778 auto &mixPlug = m_MixPlugins[numPlugins];
779 mixPlug = {};
780 mixPlug.Info.dwPluginId1 = Vst::kEffectMagic;
781 mixPlug.Info.gain = 10;
782 mixPlug.Info.szName = mpt::ToCharset(mpt::Charset::Locale, name);
783 mixPlug.Info.szLibraryName = mpt::ToCharset(mpt::Charset::UTF8, name);
784 instr.nMixPlug = numPlugins + 1;
785 instr.nMidiChannel = MidiFirstChannel;
786 instr.Transpose(-24);
787 instr.AssignSample(0);
788 // TODO: Figure out patch and routing data
789
790 numPlugins++;
791 }
792 } else
793 #endif // MPT_WITH_VST
794 if(isSynth)
795 {
796 // TODO: Figure out synth instruments
797 anySynthInstrs = true;
798 instr.AssignSample(0);
799 }
800
801 uint8 numSamples = 1;
802 static constexpr uint8 SamplesPerType[] = {1, 5, 3, 2, 4, 6, 7};
803 if(!isSynth && maskedType < std::size(SamplesPerType))
804 numSamples = SamplesPerType[maskedType];
805 if(numSamples > 1)
806 {
807 static_assert(MAX_SAMPLES > 63 * 9, "Check IFFOCT multisample code");
808 m_nSamples += numSamples - 1;
809 needInstruments = true;
810 static constexpr uint8 OctSampleMap[][8] =
811 {
812 {1, 1, 0, 0, 0, 0, 0, 0}, // 2
813 {2, 2, 1, 1, 0, 0, 0, 0}, // 3
814 {3, 3, 2, 2, 1, 0, 0, 0}, // 4
815 {4, 3, 2, 1, 1, 0, 0, 0}, // 5
816 {5, 4, 3, 2, 1, 0, 0, 0}, // 6
817 {6, 5, 4, 3, 2, 1, 0, 0}, // 7
818 };
819
820 static constexpr int8 OctTransposeMap[][8] =
821 {
822 { 0, 0, -12, -12, -24, -36, -48, -60}, // 2
823 { 0, 0, -12, -12, -24, -36, -48, -60}, // 3
824 { 0, 0, -12, -12, -24, -36, -48, -60}, // 4
825 {12, 0, -12, -24, -24, -36, -48, -60}, // 5
826 {12, 0, -12, -24, -36, -48, -48, -60}, // 6
827 {12, 0, -12, -24, -36, -48, -60, -72}, // 7
828 };
829
830 // TODO: Move octaves so that they align better (C-4 = lowest, we don't have access to the highest four octaves)
831 for(int octave = 4; octave < 10; octave++)
832 {
833 for(int note = 0; note < 12; note++)
834 {
835 instr.Keyboard[12 * octave + note] = smp + OctSampleMap[numSamples - 2][octave - 4];
836 instr.NoteMap[12 * octave + note] += OctTransposeMap[numSamples - 2][octave - 4];
837 }
838 }
839 } else if(maskedType == MMDInstrHeader::EXTSAMPLE)
840 {
841 needInstruments = true;
842 instr.Transpose(-24);
843 } else if(!isSynth && hardwareMixSamples)
844 {
845 for(int octave = 7; octave < 10; octave++)
846 {
847 for(int note = 0; note < 12; note++)
848 {
849 instr.NoteMap[12 * octave + note] -= static_cast<uint8>((octave - 6) * 12);
850 }
851 }
852 }
853
854 MMD0Sample sampleHeader;
855 sampleHeaderChunk.ReadStruct(sampleHeader);
856
857 // midiChannel = 0xFF == midi instrument but with invalid channel, midiChannel = 0x00 == sample-based instrument?
858 if(sampleHeader.midiChannel > 0 && sampleHeader.midiChannel <= 16)
859 {
860 instr.nMidiChannel = sampleHeader.midiChannel - 1 + MidiFirstChannel;
861 needInstruments = true;
862
863 #ifdef MPT_WITH_VST
864 if(!isSynth)
865 {
866 auto &mixPlug = m_MixPlugins[numPlugins];
867 mixPlug = {};
868 mixPlug.Info.dwPluginId1 = PLUGMAGIC('V', 's', 't', 'P');
869 mixPlug.Info.dwPluginId2 = PLUGMAGIC('M', 'M', 'I', 'D');
870 mixPlug.Info.gain = 10;
871 mixPlug.Info.szName = "MIDI Input Output";
872 mixPlug.Info.szLibraryName = "MIDI Input Output";
873
874 instr.nMixPlug = numPlugins + 1;
875 instr.Transpose(-24);
876
877 numPlugins++;
878 }
879 #endif // MPT_WITH_VST
880 }
881 if(sampleHeader.midiPreset > 0 && sampleHeader.midiPreset <= 128)
882 {
883 instr.nMidiProgram = sampleHeader.midiPreset;
884 }
885
886 for(SAMPLEINDEX i = 0; i < numSamples; i++)
887 {
888 ModSample &mptSmp = Samples[smp + i];
889 mptSmp.Initialize(MOD_TYPE_MED);
890 mptSmp.nVolume = 4u * std::min<uint8>(sampleHeader.sampleVolume, 64u);
891 mptSmp.RelativeTone = sampleHeader.sampleTranspose;
892 }
893
894 if(isSynth || !(loadFlags & loadSampleData))
895 {
896 smp += numSamples;
897 continue;
898 }
899
900 SampleIO sampleIO(
901 SampleIO::_8bit,
902 SampleIO::mono,
903 SampleIO::bigEndian,
904 SampleIO::signedPCM);
905
906 const bool hasLoop = sampleHeader.loopLength > 1;
907 SmpLength loopStart = sampleHeader.loopStart * 2;
908 SmpLength loopEnd = loopStart + sampleHeader.loopLength * 2;
909
910 SmpLength length = mpt::saturate_cast<SmpLength>(sampleChunk.GetLength());
911 if(instrHeader.type & MMDInstrHeader::S_16)
912 {
913 sampleIO |= SampleIO::_16bit;
914 length /= 2;
915 }
916 if (instrHeader.type & MMDInstrHeader::STEREO)
917 {
918 sampleIO |= SampleIO::stereoSplit;
919 length /= 2;
920 }
921 if(instrHeader.type & MMDInstrHeader::DELTA)
922 {
923 sampleIO |= SampleIO::deltaPCM;
924 }
925
926 if(numSamples > 1)
927 length = length / ((1u << numSamples) - 1);
928
929 for(SAMPLEINDEX i = 0; i < numSamples; i++)
930 {
931 ModSample &mptSmp = Samples[smp + i];
932
933 mptSmp.nLength = length;
934 sampleIO.ReadSample(mptSmp, sampleChunk);
935
936 if(hasLoop)
937 {
938 mptSmp.nLoopStart = loopStart;
939 mptSmp.nLoopEnd = loopEnd;
940 mptSmp.uFlags.set(CHN_LOOP);
941 }
942
943 length *= 2;
944 loopStart *= 2;
945 loopEnd *= 2;
946 }
947
948 smp += numSamples;
949 }
950
951 if(expData.instrExtOffset != 0 && expData.instrExtEntries != 0 && file.Seek(expData.instrExtOffset))
952 {
953 const uint16 entries = std::min<uint16>(expData.instrExtEntries, songHeader.numSamples);
954 const uint16 size = expData.instrExtEntrySize;
955 for(uint16 i = 0; i < entries; i++)
956 {
957 MMDInstrExt instrExt;
958 file.ReadStructPartial(instrExt, size);
959
960 ModInstrument &ins = *Instruments[i + 1];
961 if(instrExt.hold)
962 {
963 ins.VolEnv.assign({
964 EnvelopeNode{0u, ENVELOPE_MAX},
965 EnvelopeNode{static_cast<EnvelopeNode::tick_t>(instrExt.hold - 1), ENVELOPE_MAX},
966 EnvelopeNode{static_cast<EnvelopeNode::tick_t>(instrExt.hold + (instrExt.decay ? 64u / instrExt.decay : 0u)), ENVELOPE_MIN},
967 });
968 if(instrExt.hold == 1)
969 ins.VolEnv.erase(ins.VolEnv.begin());
970 ins.nFadeOut = instrExt.decay ? (instrExt.decay * 512) : 32767;
971 ins.VolEnv.dwFlags.set(ENV_ENABLED);
972 needInstruments = true;
973 }
974 if(size > offsetof(MMDInstrExt, volume))
975 ins.nGlobalVol = (instrExt.volume + 1u) / 2u;
976 if(size > offsetof(MMDInstrExt, midiBank))
977 ins.wMidiBank = instrExt.midiBank;
978 #ifdef MPT_WITH_VST
979 if(ins.nMixPlug > 0)
980 {
981 PLUGINDEX plug = ins.nMixPlug - 1;
982 auto &mixPlug = m_MixPlugins[plug];
983 if(mixPlug.Info.dwPluginId2 == PLUGMAGIC('M', 'M', 'I', 'D'))
984 {
985 float dev = (instrExt.outputDevice + 1) / 65536.0f; // Magic code from MidiInOut.h :(
986 mixPlug.pluginData.resize(3 * sizeof(uint32));
987 auto memFile = std::make_pair(mpt::as_span(mixPlug.pluginData), mpt::IO::Offset(0));
988 mpt::IO::WriteIntLE<uint32>(memFile, 0); // Plugin data type
989 mpt::IO::Write(memFile, IEEE754binary32LE{0}); // Input device
990 mpt::IO::Write(memFile, IEEE754binary32LE{dev}); // Output device
991
992 // Check if we already have another plugin referencing this output device
993 for(PLUGINDEX p = 0; p < plug; p++)
994 {
995 const auto &otherPlug = m_MixPlugins[p];
996 if(otherPlug.Info.dwPluginId1 == mixPlug.Info.dwPluginId1
997 && otherPlug.Info.dwPluginId2 == mixPlug.Info.dwPluginId2
998 && otherPlug.pluginData == mixPlug.pluginData)
999 {
1000 ins.nMixPlug = p + 1;
1001 mixPlug = {};
1002 break;
1003 }
1004 }
1005 }
1006 }
1007 #endif // MPT_WITH_VST
1008
1009 ModSample &sample = Samples[ins.Keyboard[NOTE_MIDDLEC]];
1010 sample.nFineTune = MOD2XMFineTune(instrExt.finetune);
1011
1012 if(size > offsetof(MMDInstrExt, loopLength))
1013 {
1014 sample.nLoopStart = instrExt.loopStart;
1015 sample.nLoopEnd = instrExt.loopStart + instrExt.loopLength;
1016 }
1017 if(size > offsetof(MMDInstrExt, instrFlags))
1018 {
1019 sample.uFlags.set(CHN_LOOP, (instrExt.instrFlags & MMDInstrExt::SSFLG_LOOP) != 0);
1020 sample.uFlags.set(CHN_PINGPONGLOOP, (instrExt.instrFlags & MMDInstrExt::SSFLG_PINGPONG) != 0);
1021 if(instrExt.instrFlags & MMDInstrExt::SSFLG_DISABLED)
1022 sample.nGlobalVol = 0;
1023 }
1024 }
1025 }
1026 if(expData.instrInfoOffset != 0 && expData.instrInfoEntries != 0 && file.Seek(expData.instrInfoOffset))
1027 {
1028 const uint16 entries = std::min<uint16>(expData.instrInfoEntries, songHeader.numSamples);
1029 const uint16 size = expData.instrInfoEntrySize;
1030 for(uint16 i = 0; i < entries; i++)
1031 {
1032 MMDInstrInfo instrInfo;
1033 file.ReadStructPartial(instrInfo, size);
1034 Instruments[i + 1]->name = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, instrInfo.name);
1035 for(auto smp : Instruments[i + 1]->GetSamples())
1036 {
1037 m_szNames[smp] = Instruments[i + 1]->name;
1038 }
1039 }
1040 }
1041
1042 // Setup a program change macro for command 1C (even if MIDI plugin is disabled, as otherwise these commands may act as filter commands)
1043 m_MidiCfg.ClearZxxMacros();
1044 strcpy(m_MidiCfg.szMidiSFXExt[0], "Cc z");
1045
1046 file.Rewind();
1047 PATTERNINDEX basePattern = 0;
1048 for(SEQUENCEINDEX song = 0; song < numSongs; song++)
1049 {
1050 MEDReadNextSong(file, fileHeader, expData, songHeader);
1051
1052 if(song != 0)
1053 {
1054 if(Order.AddSequence() == SEQUENCEINDEX_INVALID)
1055 return false;
1056 }
1057
1058 ModSequence &order = Order(song);
1059
1060 std::map<ORDERINDEX, ORDERINDEX> jumpTargets;
1061 order.clear();
1062 uint32 preamp = 32;
1063 if(version < 2)
1064 {
1065 if(songHeader.songLength > 256 || m_nChannels > 16)
1066 return false;
1067 ReadOrderFromArray(order, songHeader.GetMMD0Song().sequence, songHeader.songLength);
1068 for(auto &ord : order)
1069 {
1070 ord += basePattern;
1071 }
1072
1073 SetupMODPanning(true);
1074 for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
1075 {
1076 ChnSettings[chn].nVolume = std::min<uint8>(songHeader.trackVol[chn], 64);
1077 }
1078 } else
1079 {
1080 const MMD2Song header = songHeader.GetMMD2Song();
1081 if(header.numTracks < 1 || header.numTracks > 64 || m_nChannels > 64)
1082 return false;
1083
1084 const bool freePan = (header.flags3 & MMD2Song::FLAG3_FREEPAN);
1085 if(header.volAdjust)
1086 preamp = Util::muldivr_unsigned(preamp, std::min<uint16>(header.volAdjust, 800), 100);
1087 if (freePan)
1088 preamp /= 2;
1089
1090 if(file.Seek(header.trackVolsOffset))
1091 {
1092 for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
1093 {
1094 ChnSettings[chn].nVolume = std::min<uint8>(file.ReadUint8(), 64);
1095 }
1096 }
1097 if(header.trackPanOffset && file.Seek(header.trackPanOffset))
1098 {
1099 for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
1100 {
1101 ChnSettings[chn].nPan = (Clamp<int8, int8>(file.ReadInt8(), -16, 16) + 16) * 8;
1102 }
1103 } else
1104 {
1105 SetupMODPanning(true);
1106 }
1107
1108 std::vector<uint16be> sections;
1109 if(!file.Seek(header.sectionTableOffset)
1110 || !file.CanRead(songHeader.songLength * 2)
1111 || !file.ReadVector(sections, songHeader.songLength))
1112 continue;
1113
1114 for(uint16 section : sections)
1115 {
1116 if(section > header.numPlaySeqs)
1117 continue;
1118
1119 file.Seek(header.playSeqTableOffset + section * 4);
1120 if(!file.Seek(file.ReadUint32BE()) || !file.CanRead(sizeof(MMD2PlaySeq)))
1121 continue;
1122
1123 MMD2PlaySeq playSeq;
1124 file.ReadStruct(playSeq);
1125
1126 if(!order.empty())
1127 order.push_back(order.GetIgnoreIndex());
1128
1129 size_t readOrders = playSeq.length;
1130 if(!file.CanRead(readOrders))
1131 LimitMax(readOrders, file.BytesLeft());
1132 LimitMax(readOrders, ORDERINDEX_MAX);
1133
1134 size_t orderStart = order.size();
1135 order.reserve(orderStart + readOrders);
1136 for(size_t ord = 0; ord < readOrders; ord++)
1137 {
1138 PATTERNINDEX pat = file.ReadUint16BE();
1139 if(pat < 0x8000)
1140 {
1141 order.push_back(basePattern + pat);
1142 }
1143 }
1144 if(playSeq.name[0])
1145 order.SetName(mpt::ToUnicode(mpt::Charset::ISO8859_1, mpt::String::ReadAutoBuf(playSeq.name)));
1146
1147 // Play commands (jump / stop)
1148 if(playSeq.commandTableOffset > 0 && file.Seek(playSeq.commandTableOffset))
1149 {
1150 MMDPlaySeqCommand command;
1151 while(file.ReadStruct(command))
1152 {
1153 FileReader chunk = file.ReadChunk(command.extraSize);
1154 ORDERINDEX ord = mpt::saturate_cast<ORDERINDEX>(orderStart + command.offset);
1155 if(command.offset == 0xFFFF || ord >= order.size())
1156 break;
1157 if(command.command == MMDPlaySeqCommand::kStop)
1158 {
1159 order[ord] = order.GetInvalidPatIndex();
1160 } else if(command.command == MMDPlaySeqCommand::kJump)
1161 {
1162 jumpTargets[ord] = chunk.ReadUint16BE();
1163 order[ord] = order.GetIgnoreIndex();
1164 }
1165 }
1166 }
1167 }
1168 }
1169
1170 const bool volHex = (songHeader.flags & MMDSong::FLAG_VOLHEX) != 0;
1171 const bool is8Ch = (songHeader.flags & MMDSong::FLAG_8CHANNEL) != 0;
1172 const bool bpmMode = (songHeader.flags2 & MMDSong::FLAG2_BPM) != 0;
1173 const uint8 rowsPerBeat = 1 + (songHeader.flags2 & MMDSong::FLAG2_BMASK);
1174 m_nDefaultTempo = MMDTempoToBPM(songHeader.defaultTempo, is8Ch, bpmMode, rowsPerBeat);
1175 m_nDefaultSpeed = Clamp<uint8, uint8>(songHeader.tempo2, 1, 32);
1176 if(bpmMode)
1177 {
1178 m_nDefaultRowsPerBeat = rowsPerBeat;
1179 m_nDefaultRowsPerMeasure = m_nDefaultRowsPerBeat * 4u;
1180 }
1181
1182 if(songHeader.masterVol)
1183 m_nDefaultGlobalVolume = std::min<uint8>(songHeader.masterVol, 64) * 4;
1184 m_nSamplePreAmp = m_nVSTiVolume = preamp;
1185
1186 // For MED, this affects both volume and pitch slides
1187 m_SongFlags.set(SONG_FASTVOLSLIDES, !(songHeader.flags & MMDSong::FLAG_STSLIDE));
1188
1189 if(expData.songNameOffset && file.Seek(expData.songNameOffset))
1190 {
1191 file.ReadString<mpt::String::maybeNullTerminated>(m_songName, expData.songNameLength);
1192 if(numSongs > 1)
1193 order.SetName(mpt::ToUnicode(mpt::Charset::ISO8859_1, m_songName));
1194 }
1195 if(expData.annoLength > 1 && file.Seek(expData.annoText))
1196 {
1197 m_songMessage.Read(file, expData.annoLength - 1, SongMessage::leAutodetect);
1198 }
1199
1200 #ifdef MPT_WITH_VST
1201 // Read MIDI messages
1202 if(expData.midiDumpOffset && file.Seek(expData.midiDumpOffset) && file.CanRead(8))
1203 {
1204 uint16 numDumps = std::min(file.ReadUint16BE(), static_cast<uint16>(std::size(m_MidiCfg.szMidiZXXExt)));
1205 file.Skip(6);
1206 if(file.CanRead(numDumps * 4))
1207 {
1208 std::vector<uint32be> dumpPointers;
1209 file.ReadVector(dumpPointers, numDumps);
1210 for(uint16 dump = 0; dump < numDumps; dump++)
1211 {
1212 if(!file.Seek(dumpPointers[dump]) || !file.CanRead(sizeof(MMDDump)))
1213 continue;
1214 MMDDump dumpHeader;
1215 file.ReadStruct(dumpHeader);
1216 if(!file.Seek(dumpHeader.dataPointer) || !file.CanRead(dumpHeader.length))
1217 continue;
1218 auto ¯o = m_MidiCfg.szMidiZXXExt[dump];
1219 auto length = std::min(static_cast<size_t>(dumpHeader.length), std::size(macro) / 2u);
1220 for(size_t i = 0; i < length; i++)
1221 {
1222 const uint8 byte = file.ReadUint8(), high = byte >> 4, low = byte & 0x0F;
1223 macro[i * 2] = high + (high < 0x0A ? '0' : 'A' - 0x0A);
1224 macro[i * 2 + 1] = low + (low < 0x0A ? '0' : 'A' - 0x0A);
1225 }
1226 }
1227 }
1228 }
1229 #endif // MPT_WITH_VST
1230
1231 if(expData.mmdInfoOffset && file.Seek(expData.mmdInfoOffset) && file.CanRead(12))
1232 {
1233 file.Skip(6); // Next info file (unused) + reserved
1234 if(file.ReadUint16BE() == 1) // ASCII text
1235 {
1236 uint32 length = file.ReadUint32BE();
1237 if(length && file.CanRead(length))
1238 {
1239 const auto oldMsg = std::move(m_songMessage);
1240 m_songMessage.Read(file, length, SongMessage::leAutodetect);
1241 if(!oldMsg.empty())
1242 m_songMessage.SetRaw(oldMsg + std::string(2, SongMessage::InternalLineEnding) + m_songMessage);
1243 }
1244 }
1245 }
1246
1247 // Track Names
1248 if(version >= 2 && expData.trackInfoOffset)
1249 {
1250 for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
1251 {
1252 if(file.Seek(expData.trackInfoOffset + chn * 4)
1253 && file.Seek(file.ReadUint32BE()))
1254 {
1255 uint32 nameOffset = 0, nameLength = 0;
1256 while(file.CanRead(sizeof(MMDTag)))
1257 {
1258 MMDTag tag;
1259 file.ReadStruct(tag);
1260 if(tag.type == MMDTag::MMDTAG_END)
1261 break;
1262 switch(tag.type & MMDTag::MMDTAG_MASK)
1263 {
1264 case MMDTag::MMDTAG_TRK_NAME: nameOffset = tag.data; break;
1265 case MMDTag::MMDTAG_TRK_NAMELEN: nameLength = tag.data; break;
1266 }
1267 }
1268 if(nameOffset > 0 && nameLength > 0 && file.Seek(nameOffset))
1269 {
1270 file.ReadString<mpt::String::maybeNullTerminated>(ChnSettings[chn].szName, nameLength);
1271 }
1272 }
1273 }
1274 }
1275
1276 PATTERNINDEX numPatterns = songHeader.numBlocks;
1277 Patterns.ResizeArray(basePattern + numPatterns);
1278 for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
1279 {
1280 if(!(loadFlags & loadPatternData)
1281 || !file.Seek(fileHeader.blockArrOffset + pat * 4u)
1282 || !file.Seek(file.ReadUint32BE()))
1283 {
1284 continue;
1285 }
1286
1287 CHANNELINDEX numTracks;
1288 ROWINDEX numRows;
1289 std::string patName;
1290 int transpose;
1291 FileReader cmdExt;
1292
1293 if(version < 1)
1294 {
1295 transpose = NOTE_MIN + 47;
1296 MMD0PatternHeader patHeader;
1297 file.ReadStruct(patHeader);
1298 numTracks = patHeader.numTracks;
1299 numRows = patHeader.numRows + 1;
1300 } else
1301 {
1302 transpose = NOTE_MIN + (version <= 2 ? 47 : 23) + songHeader.playTranspose;
1303 MMD1PatternHeader patHeader;
1304 file.ReadStruct(patHeader);
1305 numTracks = patHeader.numTracks;
1306 numRows = patHeader.numRows + 1;
1307 if(patHeader.blockInfoOffset)
1308 {
1309 auto offset = file.GetPosition();
1310 file.Seek(patHeader.blockInfoOffset);
1311 MMDBlockInfo blockInfo;
1312 file.ReadStruct(blockInfo);
1313 if(file.Seek(blockInfo.nameOffset))
1314 {
1315 // We have now chased four pointers to get this far... lovely format.
1316 file.ReadString<mpt::String::maybeNullTerminated>(patName, blockInfo.nameLength);
1317 }
1318 if(blockInfo.cmdExtTableOffset
1319 && file.Seek(blockInfo.cmdExtTableOffset)
1320 && file.Seek(file.ReadUint32BE()))
1321 {
1322 cmdExt = file.ReadChunk(numTracks * numRows);
1323 }
1324
1325 file.Seek(offset);
1326 }
1327 }
1328
1329 if(!Patterns.Insert(basePattern + pat, numRows))
1330 continue;
1331
1332 CPattern &pattern = Patterns[basePattern + pat];
1333 pattern.SetName(patName);
1334 LimitMax(numTracks, m_nChannels);
1335
1336 for(ROWINDEX row = 0; row < numRows; row++)
1337 {
1338 ModCommand *m = pattern.GetpModCommand(row, 0);
1339 for(CHANNELINDEX chn = 0; chn < numTracks; chn++, m++)
1340 {
1341 int note = NOTE_NONE;
1342 if(version < 1)
1343 {
1344 const auto [noteInstr, instrCmd, param] = file.ReadArray<uint8, 3>();
1345
1346 if(noteInstr & 0x3F)
1347 note = (noteInstr & 0x3F) + transpose;
1348
1349 m->instr = (instrCmd >> 4) | ((noteInstr & 0x80) >> 3) | ((noteInstr & 0x40) >> 1);
1350
1351 m->command = instrCmd & 0x0F;
1352 m->param = param;
1353 } else
1354 {
1355 const auto [noteVal, instr, command, param1] = file.ReadArray<uint8, 4>();
1356 m->vol = cmdExt.ReadUint8();
1357
1358 if(noteVal & 0x7F)
1359 note = (noteVal & 0x7F) + transpose;
1360 else if(noteVal == 0x80)
1361 m->note = NOTE_NOTECUT;
1362
1363 m->instr = instr & 0x3F;
1364 m->command = command;
1365 m->param = param1;
1366 }
1367 // Octave wrapping for 4-channel modules (TODO: this should not be set because of synth instruments)
1368 if(hardwareMixSamples && note >= NOTE_MIDDLEC + 2 * 12)
1369 needInstruments = true;
1370
1371 if(note >= NOTE_MIN && note <= NOTE_MAX)
1372 m->note = static_cast<ModCommand::NOTE>(note);
1373 ConvertMEDEffect(*m, is8Ch, bpmMode, rowsPerBeat, volHex);
1374 }
1375 }
1376 }
1377
1378 // Fix jump order commands
1379 for(const auto & [from, to] : jumpTargets)
1380 {
1381 PATTERNINDEX pat;
1382 if(from > 0 && order.IsValidPat(from - 1))
1383 {
1384 pat = order.EnsureUnique(from - 1);
1385 } else
1386 {
1387 if(to == from + 1) // No action required
1388 continue;
1389 pat = Patterns.InsertAny(1);
1390 if(pat == PATTERNINDEX_INVALID)
1391 continue;
1392 order[from] = pat;
1393 }
1394 Patterns[pat].WriteEffect(EffectWriter(CMD_POSITIONJUMP, mpt::saturate_cast<ModCommand::PARAM>(to)).Row(Patterns[pat].GetNumRows() - 1).RetryPreviousRow());
1395 if(pat >= numPatterns)
1396 numPatterns = pat + 1;
1397 }
1398
1399 basePattern += numPatterns;
1400
1401 if(!expData.nextModOffset || !file.Seek(expData.nextModOffset))
1402 break;
1403 }
1404 Order.SetSequence(0);
1405
1406 if(!needInstruments)
1407 {
1408 for(INSTRUMENTINDEX ins = 1; ins <= m_nInstruments; ins++)
1409 {
1410 delete Instruments[ins];
1411 Instruments[ins] = nullptr;
1412 }
1413 m_nInstruments = 0;
1414 }
1415
1416 if(anySynthInstrs)
1417 AddToLog(LogWarning, U_("Synthesized MED instruments are not supported."));
1418
1419 const mpt::uchar *madeWithTracker = MPT_ULITERAL("");
1420 switch(version)
1421 {
1422 case 0: madeWithTracker = m_nChannels > 4 ? MPT_ULITERAL("OctaMED v2.10 (MMD0)") : MPT_ULITERAL("MED v2 (MMD0)"); break;
1423 case 1: madeWithTracker = MPT_ULITERAL("OctaMED v4 (MMD1)"); break;
1424 case 2: madeWithTracker = MPT_ULITERAL("OctaMED v5 (MMD2)"); break;
1425 case 3: madeWithTracker = MPT_ULITERAL("OctaMED Soundstudio (MMD3)"); break;
1426 }
1427
1428 m_modFormat.formatName = MPT_UFORMAT("OctaMED (MMD{})")(version);
1429 m_modFormat.type = MPT_USTRING("med");
1430 m_modFormat.madeWithTracker = madeWithTracker;
1431 m_modFormat.charset = mpt::Charset::ISO8859_1;
1432
1433 return true;
1434 }
1435
1436 OPENMPT_NAMESPACE_END
1437