123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443 |
- #include "stdafx.h"
- #include "Loaders.h"
- #ifdef MPT_WITH_VST
- #include "../mptrack/Vstplug.h"
- #include "plugins/PluginManager.h"
- #endif
- #include "mpt/io/base.hpp"
- #include "mpt/io/io.hpp"
- #include "mpt/io/io_span.hpp"
- #include "mpt/io/io_stdstream.hpp"
- #include <map>
- OPENMPT_NAMESPACE_BEGIN
- struct MMD0FileHeader
- {
- char mmd[3];
- uint8be version;
- uint32be modLength;
- uint32be songOffset;
- uint16be playerSettings1[2];
- uint32be blockArrOffset;
- uint8be flags;
- uint8be reserved1[3];
- uint32be sampleArrOffset;
- uint32be reserved2;
- uint32be expDataOffset;
- uint32be reserved3;
- char playerSettings2[11];
- uint8be extraSongs;
- };
- MPT_BINARY_STRUCT(MMD0FileHeader, 52)
- struct MMD0Sample
- {
- uint16be loopStart;
- uint16be loopLength;
- uint8be midiChannel;
- uint8be midiPreset;
- uint8be sampleVolume;
- int8be sampleTranspose;
- };
- MPT_BINARY_STRUCT(MMD0Sample, 8)
- struct MMD0Song
- {
- uint8be sequence[256];
- };
- MPT_BINARY_STRUCT(MMD0Song, 256)
- struct MMD2Song
- {
- enum Flags3
- {
- FLAG3_STEREO = 0x01,
- FLAG3_FREEPAN = 0x02,
- };
- uint32be playSeqTableOffset;
- uint32be sectionTableOffset;
- uint32be trackVolsOffset;
- uint16be numTracks;
- uint16be numPlaySeqs;
- uint32be trackPanOffset;
- uint32be flags3;
- uint16be volAdjust;
- uint16be mixChannels;
- uint8be mixEchoType;
- uint8be mixEchoDepth;
- uint16be mixEchoLength;
- int8be mixStereoSep;
- char pad0[223];
- };
- MPT_BINARY_STRUCT(MMD2Song, 256)
- struct MMDSong
- {
- enum Flags
- {
- FLAG_FILTERON = 0x01,
- FLAG_JUMPINGON = 0x02,
- FLAG_JUMP8TH = 0x04,
- FLAG_INSTRSATT = 0x08,
- FLAG_VOLHEX = 0x10,
- FLAG_STSLIDE = 0x20,
- FLAG_8CHANNEL = 0x40,
- FLAG_SLOWHQ = 0x80,
- };
- enum Flags2
- {
- FLAG2_BMASK = 0x1F,
- FLAG2_BPM = 0x20,
- FLAG2_MIX = 0x80,
- };
- uint16be numBlocks;
- uint16be songLength;
- char song[256];
- MMD0Song GetMMD0Song() const
- {
- static_assert(sizeof(MMD0Song) == sizeof(song));
- return mpt::bit_cast<MMD0Song>(song);
- }
- MMD2Song GetMMD2Song() const
- {
- static_assert(sizeof(MMD2Song) == sizeof(song));
- return mpt::bit_cast<MMD2Song>(song);
- }
- uint16be defaultTempo;
- int8be playTranspose;
- uint8be flags;
- uint8be flags2;
- uint8be tempo2;
- uint8be trackVol[16];
- uint8be masterVol;
- uint8be numSamples;
- };
- MPT_BINARY_STRUCT(MMDSong, 284)
- struct MMD2PlaySeq
- {
- char name[32];
- uint32be commandTableOffset;
- uint32be reserved;
- uint16be length;
- };
- MPT_BINARY_STRUCT(MMD2PlaySeq, 42)
- struct MMD0PatternHeader
- {
- uint8be numTracks;
- uint8be numRows;
- };
- MPT_BINARY_STRUCT(MMD0PatternHeader, 2)
- struct MMD1PatternHeader
- {
- uint16be numTracks;
- uint16be numRows;
- uint32be blockInfoOffset;
- };
- MPT_BINARY_STRUCT(MMD1PatternHeader, 8)
- struct MMDPlaySeqCommand
- {
- enum Command
- {
- kStop = 1,
- kJump = 2,
- };
- uint16be offset;
- uint8be command;
- uint8be extraSize;
- };
- MPT_BINARY_STRUCT(MMDPlaySeqCommand, 4)
- struct MMDBlockInfo
- {
- uint32be highlightMaskOffset;
- uint32be nameOffset;
- uint32be nameLength;
- uint32be pageTableOffset;
- uint32be cmdExtTableOffset;
- uint32be reserved[4];
- };
- MPT_BINARY_STRUCT(MMDBlockInfo, 36)
- struct MMDInstrHeader
- {
- enum Types
- {
- VSTI = -4,
- HIGHLIFE = -3,
- HYBRID = -2,
- SYNTHETIC = -1,
- SAMPLE = 0,
- IFF5OCT = 1,
- IFF3OCT = 2,
-
- IFF2OCT = 3,
- IFF4OCT = 4,
- IFF6OCT = 5,
- IFF7OCT = 6,
-
- EXTSAMPLE = 7,
- TYPEMASK = 0x0F,
- S_16 = 0x10,
- STEREO = 0x20,
- DELTA = 0x40,
- PACKED = 0x80,
- OBSOLETE_MD16 = 0x18,
- };
- uint32be length;
- int16be type;
- };
- MPT_BINARY_STRUCT(MMDInstrHeader, 6)
- struct MMDPackedSampleHeader
- {
- uint16be packType;
- uint16be subType;
-
-
-
-
- uint8be commonFlags;
- uint8be packerFlags;
- uint32be leftChLen;
- uint32be rightChLen;
- };
- MPT_BINARY_STRUCT(MMDPackedSampleHeader, 14)
- struct MMDInstrExt
- {
- enum
- {
- SSFLG_LOOP = 0x01,
- SSFLG_EXTPSET = 0x02,
- SSFLG_DISABLED = 0x04,
- SSFLG_PINGPONG = 0x08,
- };
- uint8be hold;
- uint8be decay;
- uint8be suppressMidiOff;
- int8be finetune;
-
- uint8be defaultPitch;
- uint8be instrFlags;
- uint16be longMidiPreset;
-
- uint8be outputDevice;
- uint8be reserved;
-
- uint32be loopStart;
- uint32be loopLength;
-
- uint8 volume;
- uint8 outputPort;
- uint16le midiBank;
- };
- MPT_BINARY_STRUCT(MMDInstrExt, 22)
- struct MMDInstrInfo
- {
- char name[40];
- };
- MPT_BINARY_STRUCT(MMDInstrInfo, 40)
- struct MMD0Exp
- {
- uint32be nextModOffset;
- uint32be instrExtOffset;
- uint16be instrExtEntries;
- uint16be instrExtEntrySize;
- uint32be annoText;
- uint32be annoLength;
- uint32be instrInfoOffset;
- uint16be instrInfoEntries;
- uint16be instrInfoEntrySize;
- uint32be jumpMask;
- uint32be rgbTable;
- uint8be channelSplit[4];
- uint32be notationInfoOffset;
- uint32be songNameOffset;
- uint32be songNameLength;
- uint32be midiDumpOffset;
- uint32be mmdInfoOffset;
- uint32be arexxOffset;
- uint32be midiCmd3xOffset;
- uint32be trackInfoOffset;
- uint32be effectInfoOffset;
- uint32be tagEnd;
- };
- MPT_BINARY_STRUCT(MMD0Exp, 80)
- struct MMDTag
- {
- enum TagType
- {
-
- MMDTAG_END = 0x00000000,
- MMDTAG_PTR = 0x80000000,
- MMDTAG_MUSTKNOW = 0x40000000,
- MMDTAG_MUSTWARN = 0x20000000,
- MMDTAG_MASK = 0x1FFFFFFF,
-
-
- MMDTAG_EXP_NUMFXGROUPS = 1,
- MMDTAG_TRK_FXGROUP = 3,
- MMDTAG_TRK_NAME = 1,
- MMDTAG_TRK_NAMELEN = 2,
-
- MMDTAG_FX_ECHOTYPE = 1,
- MMDTAG_FX_ECHOLEN = 2,
- MMDTAG_FX_ECHODEPTH = 3,
- MMDTAG_FX_STEREOSEP = 4,
- MMDTAG_FX_GROUPNAME = 5,
- MMDTAG_FX_GRPNAMELEN = 6,
- };
- uint32be type;
- uint32be data;
- };
- MPT_BINARY_STRUCT(MMDTag, 8)
- struct MMDDump
- {
- uint32be length;
- uint32be dataPointer;
- uint16be extLength;
- };
- MPT_BINARY_STRUCT(MMDDump, 10)
- static TEMPO MMDTempoToBPM(uint32 tempo, bool is8Ch, bool bpmMode, uint8 rowsPerBeat)
- {
- if(bpmMode && !is8Ch)
- {
-
-
- return TEMPO((tempo * rowsPerBeat) / 4.0);
- }
- if(is8Ch && tempo > 0)
- {
- LimitMax(tempo, 10u);
-
- static constexpr uint8 tempos[10] = {179, 164, 152, 141, 131, 123, 116, 110, 104, 99};
- return TEMPO(tempos[tempo - 1], 0);
- } else if(tempo > 0 && tempo <= 10)
- {
-
- return TEMPO((6.0 * 1773447.0 / 14500.0) / tempo);
- }
- return TEMPO(tempo / 0.264);
- }
- static void ConvertMEDEffect(ModCommand &m, bool is8ch, bool bpmMode, uint8 rowsPerBeat, bool volHex)
- {
- switch(m.command)
- {
- case 0x04:
- m.command = CMD_VIBRATO;
- m.param = (std::min<uint8>(m.param >> 3, 0x0F) << 4) | std::min<uint8>((m.param & 0x0F) * 2, 0x0F);
- break;
- case 0x08:
- m.command = CMD_NONE;
- break;
- case 0x09:
- if(m.param > 0 && m.param <= 20)
- m.command = CMD_SPEED;
- else
- m.command = CMD_NONE;
- break;
- case 0x0C:
- m.command = CMD_VOLUME;
- if(!volHex && m.param < 0x99)
- m.param = (m.param >> 4) * 10 + (m.param & 0x0F);
- else if(volHex)
- m.param = ((m.param & 0x7F) + 1) / 2;
- else
- m.command = CMD_NONE;
- break;
- case 0x0D:
- m.command = CMD_VOLUMESLIDE;
- break;
- case 0x0E:
- m.command = CMD_NONE;
- break;
- case 0x0F:
- if(m.param == 0)
- {
- m.command = CMD_PATTERNBREAK;
- } else if(m.param <= 0xF0)
- {
- m.command = CMD_TEMPO;
- if(m.param < 0x03)
- m.param = 0x70;
- else
- m.param = mpt::saturate_round<ModCommand::PARAM>(MMDTempoToBPM(m.param, is8ch, bpmMode, rowsPerBeat).ToDouble());
- #ifdef MODPLUG_TRACKER
- if(m.param < 0x20)
- m.param = 0x20;
- #endif
- } else switch(m.command)
- {
- case 0xF1:
- m.command = CMD_MODCMDEX;
- m.param = 0x93;
- break;
- case 0xF2:
- m.command = CMD_MODCMDEX;
- m.param = 0xD3;
- break;
- case 0xF3:
- m.command = CMD_MODCMDEX;
- m.param = 0x92;
- break;
- case 0xF8:
- case 0xF9:
- m.command = CMD_MODCMDEX;
- m.param = 0xF9 - m.param;
- break;
- case 0xFA:
- case 0xFB:
- case 0xFD:
- case 0xFE:
- m.command = CMD_NONE;
- break;
- case 0xFF:
- m.note = NOTE_NOTECUT;
- m.command = CMD_NONE;
- break;
- default:
- m.command = CMD_NONE;
- break;
- }
- break;
- case 0x10:
- m.command = CMD_MIDI;
- m.param |= 0x80;
- break;
- case 0x11:
- m.command = CMD_MODCMDEX;
- m.param = 0x10 | std::min<uint8>(m.param, 0x0F);
- break;
- case 0x12:
- m.command = CMD_MODCMDEX;
- m.param = 0x20 | std::min<uint8>(m.param, 0x0F);
- break;
- case 0x14:
- m.command = CMD_VIBRATO;
- m.param = (std::min<uint8>((m.param >> 4) + 1, 0x0F) << 4) | (m.param & 0x0F);
- break;
- case 0x15:
- m.command = CMD_MODCMDEX;
- m.param = 0x50 | (m.param & 0x0F);
- break;
- case 0x16:
- m.command = CMD_MODCMDEX;
- m.param = 0x60 | std::min<uint8>(m.param, 0x0F);
- break;
- case 0x18:
- m.command = CMD_MODCMDEX;
- m.param = 0xC0 | std::min<uint8>(m.param, 0x0F);
- break;
- case 0x19:
- m.command = CMD_OFFSET;
- break;
- case 0x1A:
- m.command = CMD_MODCMDEX;
- m.param = 0xA0 | std::min<uint8>(m.param, 0x0F);
- break;
- case 0x1B:
- m.command = CMD_MODCMDEX;
- m.param = 0xB0 | std::min<uint8>(m.param, 0x0F);
- break;
- case 0x1C:
- if(m.param > 0 && m.param <= 128)
- {
- m.command = CMD_MIDI;
- m.param--;
- } else
- {
- m.command = CMD_NONE;
- }
- break;
- case 0x1D:
- m.command = CMD_PATTERNBREAK;
- break;
- case 0x1E:
- m.command = CMD_MODCMDEX;
- m.param = 0xE0 | std::min<uint8>(m.param, 0x0F);
- break;
- case 0x1F:
- {
- if(m.param & 0xF0)
- {
- m.command = CMD_MODCMDEX;
- m.param = 0xD0 | (m.param >> 4);
- } else if(m.param & 0x0F)
- {
- m.command = CMD_MODCMDEX;
- m.param = 0x90 | m.param;
- } else
- {
- m.command = CMD_NONE;
- }
- break;
- }
- case 0x20:
- if(m.param == 0 && m.vol == 0)
- {
- if(m.IsNote())
- {
- m.command = CMD_S3MCMDEX;
- m.param = 0x9F;
- }
- } else
- {
-
- m.command = CMD_NONE;
- }
- break;
- case 0x29:
- if(m.vol > 0)
- {
- m.command = CMD_OFFSETPERCENTAGE;
- m.param = mpt::saturate_cast<ModCommand::PARAM>(Util::muldiv_unsigned(m.param, 0x100, m.vol));
- } else
- {
- m.command = CMD_NONE;
- }
- break;
- case 0x2E:
- if(m.param <= 0x10 || m.param >= 0xF0)
- {
- m.command = CMD_PANNING8;
- m.param = mpt::saturate_cast<ModCommand::PARAM>(((m.param ^ 0x80) - 0x70) * 8);
- } else
- {
- m.command = CMD_NONE;
- }
- break;
- default:
- if(m.command < 0x10)
- CSoundFile::ConvertModCommand(m);
- else
- m.command = CMD_NONE;
- break;
- }
- }
- #ifdef MPT_WITH_VST
- static std::wstring ReadMEDStringUTF16BE(FileReader &file)
- {
- FileReader chunk = file.ReadChunk(file.ReadUint32BE());
- std::wstring s(chunk.GetLength() / 2u, L'\0');
- for(auto &c : s)
- {
- c = chunk.ReadUint16BE();
- }
- return s;
- }
- #endif
- static void MEDReadNextSong(FileReader &file, MMD0FileHeader &fileHeader, MMD0Exp &expData, MMDSong &songHeader)
- {
- file.ReadStruct(fileHeader);
- file.Seek(fileHeader.songOffset + 63 * sizeof(MMD0Sample));
- file.ReadStruct(songHeader);
- if(fileHeader.expDataOffset && file.Seek(fileHeader.expDataOffset))
- file.ReadStruct(expData);
- else
- expData = {};
- }
- static std::pair<CHANNELINDEX, SEQUENCEINDEX> MEDScanNumChannels(FileReader &file, const uint8 version)
- {
- MMD0FileHeader fileHeader;
- MMD0Exp expData;
- MMDSong songHeader;
- file.Rewind();
- uint32 songOffset = 0;
- MEDReadNextSong(file, fileHeader, expData, songHeader);
- SEQUENCEINDEX numSongs = std::min(MAX_SEQUENCES, mpt::saturate_cast<SEQUENCEINDEX>(fileHeader.expDataOffset ? fileHeader.extraSongs + 1 : 1));
- CHANNELINDEX numChannels = 4;
-
- for(SEQUENCEINDEX song = 0; song < numSongs; song++)
- {
- const PATTERNINDEX numPatterns = songHeader.numBlocks;
- if(songHeader.numSamples > 63 || numPatterns > 0x7FFF)
- return {};
- for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
- {
- if(!file.Seek(fileHeader.blockArrOffset + pat * 4u)
- || !file.Seek(file.ReadUint32BE()))
- {
- continue;
- }
- numChannels = std::max(numChannels, static_cast<CHANNELINDEX>(version < 1 ? file.ReadUint8() : file.ReadUint16BE()));
- }
-
- if(expData.nextModOffset <= songOffset || !file.Seek(expData.nextModOffset))
- {
- numSongs = song + 1;
- break;
- }
- songOffset = expData.nextModOffset;
- MEDReadNextSong(file, fileHeader, expData, songHeader);
- }
- return {numChannels, numSongs};
- }
- static bool ValidateHeader(const MMD0FileHeader &fileHeader)
- {
- if(std::memcmp(fileHeader.mmd, "MMD", 3)
- || fileHeader.version < '0' || fileHeader.version > '3'
- || fileHeader.songOffset < sizeof(MMD0FileHeader)
- || fileHeader.songOffset > uint32_max - 63 * sizeof(MMD0Sample) - sizeof(MMDSong)
- || fileHeader.blockArrOffset < sizeof(MMD0FileHeader)
- || (fileHeader.sampleArrOffset > 0 && fileHeader.sampleArrOffset < sizeof(MMD0FileHeader))
- || fileHeader.expDataOffset > uint32_max - sizeof(MMD0Exp))
- {
- return false;
- }
- return true;
- }
- static uint64 GetHeaderMinimumAdditionalSize(const MMD0FileHeader &fileHeader)
- {
- return std::max<uint64>({ fileHeader.songOffset + 63 * sizeof(MMD0Sample) + sizeof(MMDSong),
- fileHeader.blockArrOffset,
- fileHeader.sampleArrOffset ? fileHeader.sampleArrOffset : sizeof(MMD0FileHeader),
- fileHeader.expDataOffset + sizeof(MMD0Exp) }) - sizeof(MMD0FileHeader);
- }
- CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderMED(MemoryFileReader file, const uint64 *pfilesize)
- {
- MMD0FileHeader fileHeader;
- if(!file.ReadStruct(fileHeader))
- return ProbeWantMoreData;
- if(!ValidateHeader(fileHeader))
- return ProbeFailure;
- return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
- }
- bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
- {
- file.Rewind();
- MMD0FileHeader fileHeader;
- if(!file.ReadStruct(fileHeader))
- return false;
- if(!ValidateHeader(fileHeader))
- return false;
- if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
- return false;
- if(loadFlags == onlyVerifyHeader)
- return true;
-
- InitializeGlobals(MOD_TYPE_MED);
- InitializeChannels();
- const uint8 version = fileHeader.version - '0';
- file.Seek(fileHeader.songOffset);
- FileReader sampleHeaderChunk = file.ReadChunk(63 * sizeof(MMD0Sample));
- MMDSong songHeader;
- file.ReadStruct(songHeader);
- if(songHeader.numSamples > 63 || songHeader.numBlocks > 0x7FFF)
- return false;
- MMD0Exp expData{};
- if(fileHeader.expDataOffset && file.Seek(fileHeader.expDataOffset))
- {
- file.ReadStruct(expData);
- }
- const auto [numChannels, numSongs] = MEDScanNumChannels(file, version);
- if(numChannels < 1 || numChannels > MAX_BASECHANNELS)
- return false;
- m_nChannels = numChannels;
-
- std::vector<uint32be> instrOffsets;
- if(fileHeader.sampleArrOffset)
- {
- file.Seek(fileHeader.sampleArrOffset);
- file.ReadVector(instrOffsets, songHeader.numSamples);
- } else if(songHeader.numSamples > 0)
- {
- return false;
- }
- m_nInstruments = m_nSamples = songHeader.numSamples;
-
-
-
-
- const bool hardwareMixSamples = (version < 2) || (version >= 2 && !(songHeader.flags2 & MMDSong::FLAG2_MIX));
- bool needInstruments = false;
- bool anySynthInstrs = false;
- #ifdef MPT_WITH_VST
- PLUGINDEX numPlugins = 0;
- #endif
- for(SAMPLEINDEX ins = 1, smp = 1; ins <= m_nInstruments; ins++)
- {
- if(!AllocateInstrument(ins, smp))
- return false;
- ModInstrument &instr = *Instruments[ins];
- MMDInstrHeader instrHeader{};
- FileReader sampleChunk;
- if(instrOffsets[ins - 1] != 0 && file.Seek(instrOffsets[ins - 1]))
- {
- file.ReadStruct(instrHeader);
- uint32 chunkLength = instrHeader.length;
- if(instrHeader.type > 0 && (instrHeader.type & MMDInstrHeader::STEREO))
- chunkLength *= 2u;
- sampleChunk = file.ReadChunk(chunkLength);
- }
- const bool isSynth = instrHeader.type < 0;
- const size_t maskedType = static_cast<size_t>(instrHeader.type & MMDInstrHeader::TYPEMASK);
- #ifdef MPT_WITH_VST
- if(instrHeader.type == MMDInstrHeader::VSTI)
- {
- needInstruments = true;
- sampleChunk.Skip(6);
- const std::wstring type = ReadMEDStringUTF16BE(sampleChunk);
- const std::wstring name = ReadMEDStringUTF16BE(sampleChunk);
- if(type == L"VST")
- {
- auto &mixPlug = m_MixPlugins[numPlugins];
- mixPlug = {};
- mixPlug.Info.dwPluginId1 = Vst::kEffectMagic;
- mixPlug.Info.gain = 10;
- mixPlug.Info.szName = mpt::ToCharset(mpt::Charset::Locale, name);
- mixPlug.Info.szLibraryName = mpt::ToCharset(mpt::Charset::UTF8, name);
- instr.nMixPlug = numPlugins + 1;
- instr.nMidiChannel = MidiFirstChannel;
- instr.Transpose(-24);
- instr.AssignSample(0);
-
- numPlugins++;
- }
- } else
- #endif
- if(isSynth)
- {
-
- anySynthInstrs = true;
- instr.AssignSample(0);
- }
- uint8 numSamples = 1;
- static constexpr uint8 SamplesPerType[] = {1, 5, 3, 2, 4, 6, 7};
- if(!isSynth && maskedType < std::size(SamplesPerType))
- numSamples = SamplesPerType[maskedType];
- if(numSamples > 1)
- {
- static_assert(MAX_SAMPLES > 63 * 9, "Check IFFOCT multisample code");
- m_nSamples += numSamples - 1;
- needInstruments = true;
- static constexpr uint8 OctSampleMap[][8] =
- {
- {1, 1, 0, 0, 0, 0, 0, 0},
- {2, 2, 1, 1, 0, 0, 0, 0},
- {3, 3, 2, 2, 1, 0, 0, 0},
- {4, 3, 2, 1, 1, 0, 0, 0},
- {5, 4, 3, 2, 1, 0, 0, 0},
- {6, 5, 4, 3, 2, 1, 0, 0},
- };
- static constexpr int8 OctTransposeMap[][8] =
- {
- { 0, 0, -12, -12, -24, -36, -48, -60},
- { 0, 0, -12, -12, -24, -36, -48, -60},
- { 0, 0, -12, -12, -24, -36, -48, -60},
- {12, 0, -12, -24, -24, -36, -48, -60},
- {12, 0, -12, -24, -36, -48, -48, -60},
- {12, 0, -12, -24, -36, -48, -60, -72},
- };
-
- for(int octave = 4; octave < 10; octave++)
- {
- for(int note = 0; note < 12; note++)
- {
- instr.Keyboard[12 * octave + note] = smp + OctSampleMap[numSamples - 2][octave - 4];
- instr.NoteMap[12 * octave + note] += OctTransposeMap[numSamples - 2][octave - 4];
- }
- }
- } else if(maskedType == MMDInstrHeader::EXTSAMPLE)
- {
- needInstruments = true;
- instr.Transpose(-24);
- } else if(!isSynth && hardwareMixSamples)
- {
- for(int octave = 7; octave < 10; octave++)
- {
- for(int note = 0; note < 12; note++)
- {
- instr.NoteMap[12 * octave + note] -= static_cast<uint8>((octave - 6) * 12);
- }
- }
- }
- MMD0Sample sampleHeader;
- sampleHeaderChunk.ReadStruct(sampleHeader);
-
- if(sampleHeader.midiChannel > 0 && sampleHeader.midiChannel <= 16)
- {
- instr.nMidiChannel = sampleHeader.midiChannel - 1 + MidiFirstChannel;
- needInstruments = true;
- #ifdef MPT_WITH_VST
- if(!isSynth)
- {
- auto &mixPlug = m_MixPlugins[numPlugins];
- mixPlug = {};
- mixPlug.Info.dwPluginId1 = PLUGMAGIC('V', 's', 't', 'P');
- mixPlug.Info.dwPluginId2 = PLUGMAGIC('M', 'M', 'I', 'D');
- mixPlug.Info.gain = 10;
- mixPlug.Info.szName = "MIDI Input Output";
- mixPlug.Info.szLibraryName = "MIDI Input Output";
- instr.nMixPlug = numPlugins + 1;
- instr.Transpose(-24);
- numPlugins++;
- }
- #endif
- }
- if(sampleHeader.midiPreset > 0 && sampleHeader.midiPreset <= 128)
- {
- instr.nMidiProgram = sampleHeader.midiPreset;
- }
- for(SAMPLEINDEX i = 0; i < numSamples; i++)
- {
- ModSample &mptSmp = Samples[smp + i];
- mptSmp.Initialize(MOD_TYPE_MED);
- mptSmp.nVolume = 4u * std::min<uint8>(sampleHeader.sampleVolume, 64u);
- mptSmp.RelativeTone = sampleHeader.sampleTranspose;
- }
- if(isSynth || !(loadFlags & loadSampleData))
- {
- smp += numSamples;
- continue;
- }
- SampleIO sampleIO(
- SampleIO::_8bit,
- SampleIO::mono,
- SampleIO::bigEndian,
- SampleIO::signedPCM);
- const bool hasLoop = sampleHeader.loopLength > 1;
- SmpLength loopStart = sampleHeader.loopStart * 2;
- SmpLength loopEnd = loopStart + sampleHeader.loopLength * 2;
- SmpLength length = mpt::saturate_cast<SmpLength>(sampleChunk.GetLength());
- if(instrHeader.type & MMDInstrHeader::S_16)
- {
- sampleIO |= SampleIO::_16bit;
- length /= 2;
- }
- if(instrHeader.type & MMDInstrHeader::STEREO)
- {
- sampleIO |= SampleIO::stereoSplit;
- length /= 2;
- }
- if(instrHeader.type & MMDInstrHeader::DELTA)
- {
- sampleIO |= SampleIO::deltaPCM;
- }
- if(numSamples > 1)
- length = length / ((1u << numSamples) - 1);
- for(SAMPLEINDEX i = 0; i < numSamples; i++)
- {
- ModSample &mptSmp = Samples[smp + i];
- mptSmp.nLength = length;
- sampleIO.ReadSample(mptSmp, sampleChunk);
- if(hasLoop)
- {
- mptSmp.nLoopStart = loopStart;
- mptSmp.nLoopEnd = loopEnd;
- mptSmp.uFlags.set(CHN_LOOP);
- }
- length *= 2;
- loopStart *= 2;
- loopEnd *= 2;
- }
- smp += numSamples;
- }
- if(expData.instrExtOffset != 0 && expData.instrExtEntries != 0 && file.Seek(expData.instrExtOffset))
- {
- const uint16 entries = std::min<uint16>(expData.instrExtEntries, songHeader.numSamples);
- const uint16 size = expData.instrExtEntrySize;
- for(uint16 i = 0; i < entries; i++)
- {
- MMDInstrExt instrExt;
- file.ReadStructPartial(instrExt, size);
- ModInstrument &ins = *Instruments[i + 1];
- if(instrExt.hold)
- {
- ins.VolEnv.assign({
- EnvelopeNode{0u, ENVELOPE_MAX},
- EnvelopeNode{static_cast<EnvelopeNode::tick_t>(instrExt.hold - 1), ENVELOPE_MAX},
- EnvelopeNode{static_cast<EnvelopeNode::tick_t>(instrExt.hold + (instrExt.decay ? 64u / instrExt.decay : 0u)), ENVELOPE_MIN},
- });
- if(instrExt.hold == 1)
- ins.VolEnv.erase(ins.VolEnv.begin());
- ins.nFadeOut = instrExt.decay ? (instrExt.decay * 512) : 32767;
- ins.VolEnv.dwFlags.set(ENV_ENABLED);
- needInstruments = true;
- }
- if(size > offsetof(MMDInstrExt, volume))
- ins.nGlobalVol = (instrExt.volume + 1u) / 2u;
- if(size > offsetof(MMDInstrExt, midiBank))
- ins.wMidiBank = instrExt.midiBank;
- #ifdef MPT_WITH_VST
- if(ins.nMixPlug > 0)
- {
- PLUGINDEX plug = ins.nMixPlug - 1;
- auto &mixPlug = m_MixPlugins[plug];
- if(mixPlug.Info.dwPluginId2 == PLUGMAGIC('M', 'M', 'I', 'D'))
- {
- float dev = (instrExt.outputDevice + 1) / 65536.0f;
- mixPlug.pluginData.resize(3 * sizeof(uint32));
- auto memFile = std::make_pair(mpt::as_span(mixPlug.pluginData), mpt::IO::Offset(0));
- mpt::IO::WriteIntLE<uint32>(memFile, 0);
- mpt::IO::Write(memFile, IEEE754binary32LE{0});
- mpt::IO::Write(memFile, IEEE754binary32LE{dev});
-
- for(PLUGINDEX p = 0; p < plug; p++)
- {
- const auto &otherPlug = m_MixPlugins[p];
- if(otherPlug.Info.dwPluginId1 == mixPlug.Info.dwPluginId1
- && otherPlug.Info.dwPluginId2 == mixPlug.Info.dwPluginId2
- && otherPlug.pluginData == mixPlug.pluginData)
- {
- ins.nMixPlug = p + 1;
- mixPlug = {};
- break;
- }
- }
- }
- }
- #endif
- ModSample &sample = Samples[ins.Keyboard[NOTE_MIDDLEC]];
- sample.nFineTune = MOD2XMFineTune(instrExt.finetune);
- if(size > offsetof(MMDInstrExt, loopLength))
- {
- sample.nLoopStart = instrExt.loopStart;
- sample.nLoopEnd = instrExt.loopStart + instrExt.loopLength;
- }
- if(size > offsetof(MMDInstrExt, instrFlags))
- {
- sample.uFlags.set(CHN_LOOP, (instrExt.instrFlags & MMDInstrExt::SSFLG_LOOP) != 0);
- sample.uFlags.set(CHN_PINGPONGLOOP, (instrExt.instrFlags & MMDInstrExt::SSFLG_PINGPONG) != 0);
- if(instrExt.instrFlags & MMDInstrExt::SSFLG_DISABLED)
- sample.nGlobalVol = 0;
- }
- }
- }
- if(expData.instrInfoOffset != 0 && expData.instrInfoEntries != 0 && file.Seek(expData.instrInfoOffset))
- {
- const uint16 entries = std::min<uint16>(expData.instrInfoEntries, songHeader.numSamples);
- const uint16 size = expData.instrInfoEntrySize;
- for(uint16 i = 0; i < entries; i++)
- {
- MMDInstrInfo instrInfo;
- file.ReadStructPartial(instrInfo, size);
- Instruments[i + 1]->name = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, instrInfo.name);
- for(auto smp : Instruments[i + 1]->GetSamples())
- {
- m_szNames[smp] = Instruments[i + 1]->name;
- }
- }
- }
-
- m_MidiCfg.ClearZxxMacros();
- m_MidiCfg.SFx[0] = "Cc z";
- file.Rewind();
- PATTERNINDEX basePattern = 0;
- for(SEQUENCEINDEX song = 0; song < numSongs; song++)
- {
- MEDReadNextSong(file, fileHeader, expData, songHeader);
- if(song != 0)
- {
- if(Order.AddSequence() == SEQUENCEINDEX_INVALID)
- return false;
- }
- ModSequence &order = Order(song);
- std::map<ORDERINDEX, ORDERINDEX> jumpTargets;
- order.clear();
- uint32 preamp = 32;
- if(version < 2)
- {
- if(songHeader.songLength > 256 || m_nChannels > 16)
- return false;
- ReadOrderFromArray(order, songHeader.GetMMD0Song().sequence, songHeader.songLength);
- for(auto &ord : order)
- {
- ord += basePattern;
- }
- SetupMODPanning(true);
- for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
- {
- ChnSettings[chn].nVolume = std::min<uint8>(songHeader.trackVol[chn], 64);
- }
- } else
- {
- const MMD2Song header = songHeader.GetMMD2Song();
- if(header.numTracks < 1 || header.numTracks > 64 || m_nChannels > 64)
- return false;
- const bool freePan = (header.flags3 & MMD2Song::FLAG3_FREEPAN);
- if(header.volAdjust)
- preamp = Util::muldivr_unsigned(preamp, std::min<uint16>(header.volAdjust, 800), 100);
- if (freePan)
- preamp /= 2;
- if(file.Seek(header.trackVolsOffset))
- {
- for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
- {
- ChnSettings[chn].nVolume = std::min<uint8>(file.ReadUint8(), 64);
- }
- }
- if(header.trackPanOffset && file.Seek(header.trackPanOffset))
- {
- for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
- {
- ChnSettings[chn].nPan = (Clamp<int8, int8>(file.ReadInt8(), -16, 16) + 16) * 8;
- }
- } else
- {
- SetupMODPanning(true);
- }
- std::vector<uint16be> sections;
- if(!file.Seek(header.sectionTableOffset)
- || !file.CanRead(songHeader.songLength * 2)
- || !file.ReadVector(sections, songHeader.songLength))
- continue;
- for(uint16 section : sections)
- {
- if(section > header.numPlaySeqs)
- continue;
- file.Seek(header.playSeqTableOffset + section * 4);
- if(!file.Seek(file.ReadUint32BE()) || !file.CanRead(sizeof(MMD2PlaySeq)))
- continue;
- MMD2PlaySeq playSeq;
- file.ReadStruct(playSeq);
- if(!order.empty())
- order.push_back(order.GetIgnoreIndex());
- size_t readOrders = playSeq.length;
- if(!file.CanRead(readOrders))
- LimitMax(readOrders, file.BytesLeft());
- LimitMax(readOrders, ORDERINDEX_MAX);
- size_t orderStart = order.size();
- order.reserve(orderStart + readOrders);
- for(size_t ord = 0; ord < readOrders; ord++)
- {
- PATTERNINDEX pat = file.ReadUint16BE();
- if(pat < 0x8000)
- {
- order.push_back(basePattern + pat);
- }
- }
- if(playSeq.name[0])
- order.SetName(mpt::ToUnicode(mpt::Charset::Amiga_no_C1, mpt::String::ReadAutoBuf(playSeq.name)));
-
- if(playSeq.commandTableOffset > 0 && file.Seek(playSeq.commandTableOffset))
- {
- MMDPlaySeqCommand command;
- while(file.ReadStruct(command))
- {
- FileReader chunk = file.ReadChunk(command.extraSize);
- ORDERINDEX ord = mpt::saturate_cast<ORDERINDEX>(orderStart + command.offset);
- if(command.offset == 0xFFFF || ord >= order.size())
- break;
- if(command.command == MMDPlaySeqCommand::kStop)
- {
- order[ord] = order.GetInvalidPatIndex();
- } else if(command.command == MMDPlaySeqCommand::kJump)
- {
- jumpTargets[ord] = chunk.ReadUint16BE();
- order[ord] = order.GetIgnoreIndex();
- }
- }
- }
- }
- }
- const bool volHex = (songHeader.flags & MMDSong::FLAG_VOLHEX) != 0;
- const bool is8Ch = (songHeader.flags & MMDSong::FLAG_8CHANNEL) != 0;
- const bool bpmMode = (songHeader.flags2 & MMDSong::FLAG2_BPM) != 0;
- const uint8 rowsPerBeat = 1 + (songHeader.flags2 & MMDSong::FLAG2_BMASK);
- m_nDefaultTempo = MMDTempoToBPM(songHeader.defaultTempo, is8Ch, bpmMode, rowsPerBeat);
- m_nDefaultSpeed = Clamp<uint8, uint8>(songHeader.tempo2, 1, 32);
- if(bpmMode)
- {
- m_nDefaultRowsPerBeat = rowsPerBeat;
- m_nDefaultRowsPerMeasure = m_nDefaultRowsPerBeat * 4u;
- }
- if(songHeader.masterVol)
- m_nDefaultGlobalVolume = std::min<uint8>(songHeader.masterVol, 64) * 4;
- m_nSamplePreAmp = m_nVSTiVolume = preamp;
-
- m_SongFlags.set(SONG_FASTVOLSLIDES, !(songHeader.flags & MMDSong::FLAG_STSLIDE));
- if(expData.songNameOffset && file.Seek(expData.songNameOffset))
- {
- file.ReadString<mpt::String::maybeNullTerminated>(m_songName, expData.songNameLength);
- if(numSongs > 1)
- order.SetName(mpt::ToUnicode(mpt::Charset::Amiga_no_C1, m_songName));
- }
- if(expData.annoLength > 1 && file.Seek(expData.annoText))
- {
- m_songMessage.Read(file, expData.annoLength - 1, SongMessage::leAutodetect);
- }
- #ifdef MPT_WITH_VST
-
- if(expData.midiDumpOffset && file.Seek(expData.midiDumpOffset) && file.CanRead(8))
- {
- uint16 numDumps = std::min(file.ReadUint16BE(), static_cast<uint16>(m_MidiCfg.Zxx.size()));
- file.Skip(6);
- if(file.CanRead(numDumps * 4))
- {
- std::vector<uint32be> dumpPointers;
- file.ReadVector(dumpPointers, numDumps);
- for(uint16 dump = 0; dump < numDumps; dump++)
- {
- if(!file.Seek(dumpPointers[dump]) || !file.CanRead(sizeof(MMDDump)))
- continue;
- MMDDump dumpHeader;
- file.ReadStruct(dumpHeader);
- if(!file.Seek(dumpHeader.dataPointer) || !file.CanRead(dumpHeader.length))
- continue;
- std::array<char, kMacroLength> macro{};
- auto length = std::min(static_cast<size_t>(dumpHeader.length), macro.size() / 2u);
- for(size_t i = 0; i < length; i++)
- {
- const uint8 byte = file.ReadUint8(), high = byte >> 4, low = byte & 0x0F;
- macro[i * 2] = high + (high < 0x0A ? '0' : 'A' - 0x0A);
- macro[i * 2 + 1] = low + (low < 0x0A ? '0' : 'A' - 0x0A);
- }
- m_MidiCfg.Zxx[dump] = std::string_view{macro.data(), length * 2};
- }
- }
- }
- #endif
- if(expData.mmdInfoOffset && file.Seek(expData.mmdInfoOffset) && file.CanRead(12))
- {
- file.Skip(6);
- if(file.ReadUint16BE() == 1)
- {
- uint32 length = file.ReadUint32BE();
- if(length && file.CanRead(length))
- {
- const auto oldMsg = std::move(m_songMessage);
- m_songMessage.Read(file, length, SongMessage::leAutodetect);
- if(!oldMsg.empty())
- m_songMessage.SetRaw(oldMsg + std::string(2, SongMessage::InternalLineEnding) + m_songMessage);
- }
- }
- }
-
- if(version >= 2 && expData.trackInfoOffset)
- {
- for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
- {
- if(file.Seek(expData.trackInfoOffset + chn * 4)
- && file.Seek(file.ReadUint32BE()))
- {
- uint32 nameOffset = 0, nameLength = 0;
- while(file.CanRead(sizeof(MMDTag)))
- {
- MMDTag tag;
- file.ReadStruct(tag);
- if(tag.type == MMDTag::MMDTAG_END)
- break;
- switch(tag.type & MMDTag::MMDTAG_MASK)
- {
- case MMDTag::MMDTAG_TRK_NAME: nameOffset = tag.data; break;
- case MMDTag::MMDTAG_TRK_NAMELEN: nameLength = tag.data; break;
- }
- }
- if(nameOffset > 0 && nameLength > 0 && file.Seek(nameOffset))
- {
- file.ReadString<mpt::String::maybeNullTerminated>(ChnSettings[chn].szName, nameLength);
- }
- }
- }
- }
- PATTERNINDEX numPatterns = songHeader.numBlocks;
- Patterns.ResizeArray(basePattern + numPatterns);
- for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
- {
- if(!(loadFlags & loadPatternData)
- || !file.Seek(fileHeader.blockArrOffset + pat * 4u)
- || !file.Seek(file.ReadUint32BE()))
- {
- continue;
- }
- CHANNELINDEX numTracks;
- ROWINDEX numRows;
- std::string patName;
- int transpose;
- FileReader cmdExt;
- if(version < 1)
- {
- transpose = NOTE_MIN + 47;
- MMD0PatternHeader patHeader;
- file.ReadStruct(patHeader);
- numTracks = patHeader.numTracks;
- numRows = patHeader.numRows + 1;
- } else
- {
- transpose = NOTE_MIN + (version <= 2 ? 47 : 23) + songHeader.playTranspose;
- MMD1PatternHeader patHeader;
- file.ReadStruct(patHeader);
- numTracks = patHeader.numTracks;
- numRows = patHeader.numRows + 1;
- if(patHeader.blockInfoOffset)
- {
- auto offset = file.GetPosition();
- file.Seek(patHeader.blockInfoOffset);
- MMDBlockInfo blockInfo;
- file.ReadStruct(blockInfo);
- if(file.Seek(blockInfo.nameOffset))
- {
-
- file.ReadString<mpt::String::maybeNullTerminated>(patName, blockInfo.nameLength);
- }
- if(blockInfo.cmdExtTableOffset
- && file.Seek(blockInfo.cmdExtTableOffset)
- && file.Seek(file.ReadUint32BE()))
- {
- cmdExt = file.ReadChunk(numTracks * numRows);
- }
- file.Seek(offset);
- }
- }
- if(!Patterns.Insert(basePattern + pat, numRows))
- continue;
- CPattern &pattern = Patterns[basePattern + pat];
- pattern.SetName(patName);
- LimitMax(numTracks, m_nChannels);
- for(ROWINDEX row = 0; row < numRows; row++)
- {
- ModCommand *m = pattern.GetpModCommand(row, 0);
- for(CHANNELINDEX chn = 0; chn < numTracks; chn++, m++)
- {
- int note = NOTE_NONE;
- if(version < 1)
- {
- const auto [noteInstr, instrCmd, param] = file.ReadArray<uint8, 3>();
- if(noteInstr & 0x3F)
- note = (noteInstr & 0x3F) + transpose;
- m->instr = (instrCmd >> 4) | ((noteInstr & 0x80) >> 3) | ((noteInstr & 0x40) >> 1);
- m->command = instrCmd & 0x0F;
- m->param = param;
- } else
- {
- const auto [noteVal, instr, command, param1] = file.ReadArray<uint8, 4>();
- m->vol = cmdExt.ReadUint8();
- if(noteVal & 0x7F)
- note = (noteVal & 0x7F) + transpose;
- else if(noteVal == 0x80)
- m->note = NOTE_NOTECUT;
- m->instr = instr & 0x3F;
- m->command = command;
- m->param = param1;
- }
-
- if(hardwareMixSamples && note >= NOTE_MIDDLEC + 2 * 12)
- needInstruments = true;
- if(note >= NOTE_MIN && note <= NOTE_MAX)
- m->note = static_cast<ModCommand::NOTE>(note);
- ConvertMEDEffect(*m, is8Ch, bpmMode, rowsPerBeat, volHex);
- }
- }
- }
-
- for(const auto & [from, to] : jumpTargets)
- {
- PATTERNINDEX pat;
- if(from > 0 && order.IsValidPat(from - 1))
- {
- pat = order.EnsureUnique(from - 1);
- } else
- {
- if(to == from + 1)
- continue;
- pat = Patterns.InsertAny(1);
- if(pat == PATTERNINDEX_INVALID)
- continue;
- order[from] = pat;
- }
- Patterns[pat].WriteEffect(EffectWriter(CMD_POSITIONJUMP, mpt::saturate_cast<ModCommand::PARAM>(to)).Row(Patterns[pat].GetNumRows() - 1).RetryPreviousRow());
- if(pat >= numPatterns)
- numPatterns = pat + 1;
- }
- basePattern += numPatterns;
-
- if(!expData.nextModOffset || !file.Seek(expData.nextModOffset))
- break;
- }
- Order.SetSequence(0);
- if(!needInstruments)
- {
- for(INSTRUMENTINDEX ins = 1; ins <= m_nInstruments; ins++)
- {
- delete Instruments[ins];
- Instruments[ins] = nullptr;
- }
- m_nInstruments = 0;
- }
- if(anySynthInstrs)
- AddToLog(LogWarning, U_("Synthesized MED instruments are not supported."));
- const mpt::uchar *madeWithTracker = MPT_ULITERAL("");
- switch(version)
- {
- case 0: madeWithTracker = m_nChannels > 4 ? MPT_ULITERAL("OctaMED v2.10 (MMD0)") : MPT_ULITERAL("MED v2 (MMD0)"); break;
- case 1: madeWithTracker = MPT_ULITERAL("OctaMED v4 (MMD1)"); break;
- case 2: madeWithTracker = MPT_ULITERAL("OctaMED v5 (MMD2)"); break;
- case 3: madeWithTracker = MPT_ULITERAL("OctaMED Soundstudio (MMD3)"); break;
- }
- m_modFormat.formatName = MPT_UFORMAT("OctaMED (MMD{})")(version);
- m_modFormat.type = MPT_USTRING("med");
- m_modFormat.madeWithTracker = madeWithTracker;
- m_modFormat.charset = mpt::Charset::Amiga_no_C1;
- return true;
- }
- OPENMPT_NAMESPACE_END
|