123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173 |
- /*
- * Load_mt2.cpp
- * ------------
- * Purpose: MT2 (MadTracker 2) module loader
- * Notes : A couple of things are not handled properly or not at all, such as internal effects and automation envelopes
- * Authors: Olivier Lapicque
- * OpenMPT Devs
- * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
- */
- #include "stdafx.h"
- #include "Loaders.h"
- #ifdef MPT_EXTERNAL_SAMPLES
- // For loading external samples
- #include "../common/mptPathString.h"
- #endif // MPT_EXTERNAL_SAMPLES
- #ifdef MPT_WITH_VST
- #include "../mptrack/Vstplug.h"
- #endif // MPT_WITH_VST
- OPENMPT_NAMESPACE_BEGIN
- struct MT2FileHeader
- {
- enum MT2HeaderFlags
- {
- packedPatterns = 0x01,
- automation = 0x02,
- drumsAutomation = 0x08,
- masterAutomation = 0x10,
- };
- char signature[4]; // "MT20"
- uint32le userID;
- uint16le version;
- char trackerName[32]; // "MadTracker 2.0"
- char songName[64];
- uint16le numOrders;
- uint16le restartPos;
- uint16le numPatterns;
- uint16le numChannels;
- uint16le samplesPerTick;
- uint8le ticksPerLine;
- uint8le linesPerBeat;
- uint32le flags; // See HeaderFlags
- uint16le numInstruments;
- uint16le numSamples;
- };
- MPT_BINARY_STRUCT(MT2FileHeader, 126)
- struct MT2DrumsData
- {
- uint16le numDrumPatterns;
- uint16le DrumSamples[8];
- uint8le DrumPatternOrder[256];
- };
- MPT_BINARY_STRUCT(MT2DrumsData, 274)
- struct MT2TrackSettings
- {
- uint16le volume;
- uint8le trackfx; // Built-in effect type is used
- uint8le output;
- uint16le fxID;
- uint16le trackEffectParam[64][8];
- };
- MPT_BINARY_STRUCT(MT2TrackSettings, 1030)
- struct MT2Command
- {
- uint8 note; // 0=nothing, 97=note off
- uint8 instr;
- uint8 vol;
- uint8 pan;
- uint8 fxcmd;
- uint8 fxparam1;
- uint8 fxparam2;
- };
- MPT_BINARY_STRUCT(MT2Command, 7)
- struct MT2EnvPoint
- {
- uint16le x;
- uint16le y;
- };
- MPT_BINARY_STRUCT(MT2EnvPoint, 4)
- struct MT2Instrument
- {
- enum EnvTypes
- {
- VolumeEnv = 1,
- PanningEnv = 2,
- PitchEnv = 4,
- FilterEnv = 8,
- };
- uint16le numSamples;
- uint8le groupMap[96];
- uint8le vibtype, vibsweep, vibdepth, vibrate;
- uint16le fadeout;
- uint16le nna;
- };
- MPT_BINARY_STRUCT(MT2Instrument, 106)
- struct MT2IEnvelope
- {
- uint8le flags;
- uint8le numPoints;
- uint8le sustainPos;
- uint8le loopStart;
- uint8le loopEnd;
- uint8le reserved[3];
- MT2EnvPoint points[16];
- };
- MPT_BINARY_STRUCT(MT2IEnvelope, 72)
- // Note: The order of these fields differs a bit in MTIOModule_MT2.cpp - maybe just typos, I'm not sure.
- // This struct follows the save format of MadTracker 2.6.1.
- struct MT2InstrSynth
- {
- uint8le synthID;
- uint8le effectID; // 0 = Lowpass filter, 1 = Highpass filter
- uint16le cutoff; // 100...11000 Hz
- uint8le resonance; // 0...128
- uint8le attack; // 0...128
- uint8le decay; // 0...128
- uint8le midiChannel; // 0...15
- int8le device; // VST slot (positive) or MIDI device (negative)
- int8le unknown1; // Missing in MTIOModule_MT2.cpp
- uint8le volume; // 0...255
- int8le finetune; // -96...96
- int8le transpose; // -48...48
- uint8le unknown2; // Seems to be equal to instrument number.
- uint8le unknown3;
- uint8le midiProgram;
- uint8le reserved[16];
- };
- MPT_BINARY_STRUCT(MT2InstrSynth, 32)
- struct MT2Sample
- {
- uint32le length;
- uint32le frequency;
- uint8le depth;
- uint8le channels;
- uint8le flags;
- uint8le loopType;
- uint32le loopStart;
- uint32le loopEnd;
- uint16le volume;
- int8le panning;
- int8le note;
- int16le spb;
- };
- MPT_BINARY_STRUCT(MT2Sample, 26)
- struct MT2Group
- {
- uint8le sample;
- uint8le vol; // 0...128
- int8le pitch; // -128...127
- uint8le reserved[5];
- };
- MPT_BINARY_STRUCT(MT2Group, 8)
- struct MT2VST
- {
- char dll[64];
- char programName[28];
- uint32le fxID;
- uint32le fxVersion;
- uint32le programNr;
- uint8le useChunks;
- uint8le track;
- int8le pan; // Not imported - could use pan mix mode for D/W ratio, but this is not implemented for instrument plugins!
- char reserved[17];
- uint32le n;
- };
- MPT_BINARY_STRUCT(MT2VST, 128)
- static bool ConvertMT2Command(CSoundFile *that, ModCommand &m, MT2Command &p)
- {
- bool hasLegacyTempo = false;
- // Note
- m.note = NOTE_NONE;
- if(p.note) m.note = (p.note > 96) ? NOTE_KEYOFF : (p.note + NOTE_MIN + 11);
- // Instrument
- m.instr = p.instr;
- // Volume Column
- if(p.vol >= 0x10 && p.vol <= 0x90)
- {
- m.volcmd = VOLCMD_VOLUME;
- m.vol = (p.vol - 0x10) / 2;
- } else if(p.vol >= 0xA0 && p.vol <= 0xAF)
- {
- m.volcmd = VOLCMD_VOLSLIDEDOWN;
- m.vol = (p.vol & 0x0F);
- } else if(p.vol >= 0xB0 && p.vol <= 0xBF)
- {
- m.volcmd = VOLCMD_VOLSLIDEUP;
- m.vol = (p.vol & 0x0F);
- } else if(p.vol >= 0xC0 && p.vol <= 0xCF)
- {
- m.volcmd = VOLCMD_FINEVOLDOWN;
- m.vol = (p.vol & 0x0F);
- } else if(p.vol >= 0xD0 && p.vol <= 0xDF)
- {
- m.volcmd = VOLCMD_FINEVOLUP;
- m.vol = (p.vol & 0x0F);
- }
- // Effects
- if(p.fxcmd || p.fxparam1 || p.fxparam2)
- {
- switch(p.fxcmd)
- {
- case 0x00: // FastTracker effect
- m.command = p.fxparam2;
- m.param = p.fxparam1;
- CSoundFile::ConvertModCommand(m);
- #ifdef MODPLUG_TRACKER
- m.Convert(MOD_TYPE_XM, MOD_TYPE_IT, *that);
- #else
- MPT_UNREFERENCED_PARAMETER(that);
- #endif // MODPLUG_TRACKER
- if(p.fxparam2 == 0x0F)
- hasLegacyTempo = true;
- break;
- case 0x01: // Portamento up (on every tick)
- m.command = CMD_PORTAMENTOUP;
- m.param = mpt::saturate_cast<ModCommand::PARAM>((p.fxparam2 << 4) | (p.fxparam1 >> 4));
- break;
- case 0x02: // Portamento down (on every tick)
- m.command = CMD_PORTAMENTODOWN;
- m.param = mpt::saturate_cast<ModCommand::PARAM>((p.fxparam2 << 4) | (p.fxparam1 >> 4));
- break;
- case 0x03: // Tone Portamento (on every tick)
- m.command = CMD_TONEPORTAMENTO;
- m.param = mpt::saturate_cast<ModCommand::PARAM>((p.fxparam2 << 4) | (p.fxparam1 >> 4));
- break;
- case 0x04: // Vibrato
- m.command = CMD_VIBRATO;
- m.param = (p.fxparam2 & 0xF0) | (p.fxparam1 >> 4);
- break;
- case 0x08: // Panning + Polarity (we can only import panning for now)
- if(p.fxparam1)
- {
- m.command = CMD_PANNING8;
- m.param = p.fxparam1;
- } else if(p.fxparam2 == 1 || p.fxparam2 == 2)
- {
- // Invert left or right channel
- m.command = CMD_S3MCMDEX;
- m.param = 0x91;
- }
- break;
- case 0x0C: // Set volume (0x80 = 100%)
- m.command = CMD_VOLUME;
- m.param = p.fxparam2 / 2;
- break;
- case 0x0F: // Set tempo, LPB and ticks (we can only import tempo for now)
- if(p.fxparam2 != 0)
- {
- m.command = CMD_TEMPO;
- m.param = p.fxparam2;
- } else
- {
- m.command = CMD_SPEED;
- m.param = (p.fxparam1 & 0x0F);
- }
- break;
- case 0x10: // Impulse Tracker effect
- m.command = p.fxparam2;
- m.param = p.fxparam1;
- CSoundFile::S3MConvert(m, true);
- if(m.command == CMD_TEMPO || m.command == CMD_SPEED)
- hasLegacyTempo = true;
- break;
- case 0x1D: // Gapper (like IT Tremor with old FX, i.e. 1D 00 XY = ontime X + 1 ticks, offtime Y + 1 ticks)
- m.command = CMD_TREMOR;
- m.param = p.fxparam1;
- break;
- case 0x20: // Cutoff + Resonance (we can only import cutoff for now)
- m.command = CMD_MIDI;
- m.param = p.fxparam2 >> 1;
- break;
-
- case 0x22: // Cutoff + Resonance + Attack + Decay (we can only import cutoff for now)
- m.command = CMD_MIDI;
- m.param = (p.fxparam2 & 0xF0) >> 1;
- break;
- case 0x24: // Reverse
- m.command = CMD_S3MCMDEX;
- m.param = 0x9F;
- break;
- case 0x80: // Track volume
- m.command = CMD_CHANNELVOLUME;
- m.param = p.fxparam2 / 4u;
- break;
- case 0x9D: // Offset + delay
- m.volcmd = VOLCMD_OFFSET;
- m.vol = p.fxparam2 >> 3;
- m.command = CMD_S3MCMDEX;
- m.param = 0xD0 | std::min(p.fxparam1, uint8(0x0F));
- break;
- case 0xCC: // MIDI CC
- //m.command = CMD_MIDI;
- break;
- // TODO: More MT2 Effects
- }
- }
- if(p.pan)
- {
- if(m.command == CMD_NONE)
- {
- m.command = CMD_PANNING8;
- m.param = p.pan;
- } else if(m.volcmd == VOLCMD_NONE)
- {
- m.volcmd = VOLCMD_PANNING;
- m.vol = p.pan / 4;
- }
- }
- return hasLegacyTempo;
- }
- // This doesn't really do anything but skipping the envelope chunk at the moment.
- static void ReadMT2Automation(uint16 version, FileReader &file)
- {
- uint32 flags;
- uint32 trkfxid;
- if(version >= 0x203)
- {
- flags = file.ReadUint32LE();
- trkfxid = file.ReadUint32LE();
- } else
- {
- flags = file.ReadUint16LE();
- trkfxid = file.ReadUint16LE();
- }
- MPT_UNREFERENCED_PARAMETER(trkfxid);
- while(flags != 0)
- {
- if(flags & 1)
- {
- file.Skip(4 + sizeof(MT2EnvPoint) * 64);
- }
- flags >>= 1;
- }
- }
- static bool ValidateHeader(const MT2FileHeader &fileHeader)
- {
- if(std::memcmp(fileHeader.signature, "MT20", 4)
- || fileHeader.version < 0x200 || fileHeader.version >= 0x300
- || fileHeader.numChannels < 1 || fileHeader.numChannels > 64
- || fileHeader.numOrders > 256
- || fileHeader.numInstruments >= MAX_INSTRUMENTS
- || fileHeader.numSamples >= MAX_SAMPLES
- )
- {
- return false;
- }
- return true;
- }
- static uint64 GetHeaderMinimumAdditionalSize(const MT2FileHeader &fileHeader)
- {
- MPT_UNREFERENCED_PARAMETER(fileHeader);
- return 256;
- }
- CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderMT2(MemoryFileReader file, const uint64 *pfilesize)
- {
- MT2FileHeader fileHeader;
- if(!file.ReadStruct(fileHeader))
- {
- return ProbeWantMoreData;
- }
- if(!ValidateHeader(fileHeader))
- {
- return ProbeFailure;
- }
- return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
- }
- bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags)
- {
- file.Rewind();
- MT2FileHeader 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_MT2);
- InitializeChannels();
- m_modFormat.formatName = MPT_UFORMAT("MadTracker {}.{}")(fileHeader.version >> 8, mpt::ufmt::hex0<2>(fileHeader.version & 0xFF));
- m_modFormat.type = U_("mt2");
- m_modFormat.madeWithTracker = mpt::ToUnicode(mpt::Charset::Windows1252, mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.trackerName));
- m_modFormat.charset = mpt::Charset::Windows1252;
- m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songName);
- m_nChannels = fileHeader.numChannels;
- m_nDefaultSpeed = Clamp<uint8, uint8>(fileHeader.ticksPerLine, 1, 31);
- m_nDefaultTempo.Set(125);
- m_SongFlags = SONG_LINEARSLIDES | SONG_ITCOMPATGXX | SONG_EXFILTERRANGE;
- m_nInstruments = fileHeader.numInstruments;
- m_nSamples = fileHeader.numSamples;
- m_nDefaultRowsPerBeat = Clamp<uint8, uint8>(fileHeader.linesPerBeat, 1, 32);
- m_nDefaultRowsPerMeasure = m_nDefaultRowsPerBeat * 4;
- m_nVSTiVolume = 48;
- m_nSamplePreAmp = 48 * 2; // Double pre-amp because we will halve the volume of all non-drum instruments, because the volume of drum samples can exceed that of normal samples
- uint8 orders[256];
- file.ReadArray(orders);
- ReadOrderFromArray(Order(), orders, fileHeader.numOrders);
- Order().SetRestartPos(fileHeader.restartPos);
- // This value is supposed to be the size of the drums data, but in old MT2.0 files it's 8 bytes too small.
- // MadTracker itself unconditionally reads 274 bytes here if the value is != 0, so we do the same.
- const bool hasDrumChannels = file.ReadUint16LE() != 0;
- FileReader drumData = file.ReadChunk(hasDrumChannels ? sizeof(MT2DrumsData) : 0);
- FileReader extraData = file.ReadChunk(file.ReadUint32LE());
- const CHANNELINDEX channelsWithoutDrums = m_nChannels;
- static_assert(MAX_BASECHANNELS >= 64 + 8);
- if(hasDrumChannels)
- {
- m_nChannels += 8;
- }
- bool hasLegacyTempo = false;
- // Read patterns
- if(loadFlags & loadPatternData)
- Patterns.ResizeArray(fileHeader.numPatterns);
- for(PATTERNINDEX pat = 0; pat < fileHeader.numPatterns; pat++)
- {
- ROWINDEX numRows = file.ReadUint16LE();
- FileReader chunk = file.ReadChunk((file.ReadUint32LE() + 1) & ~1);
- LimitMax(numRows, MAX_PATTERN_ROWS);
- if(!numRows
- || !(loadFlags & loadPatternData)
- || !Patterns.Insert(pat, numRows))
- {
- continue;
- }
- if(fileHeader.flags & MT2FileHeader::packedPatterns)
- {
- ROWINDEX row = 0;
- CHANNELINDEX chn = 0;
- while(chunk.CanRead(1))
- {
- MT2Command cmd;
- uint8 infobyte = chunk.ReadUint8();
- uint8 repeatCount = 0;
- if(infobyte == 0xFF)
- {
- repeatCount = chunk.ReadUint8();
- infobyte = chunk.ReadUint8();
- }
- if(infobyte & 0x7F)
- {
- ModCommand *m = Patterns[pat].GetpModCommand(row, chn);
- MemsetZero(cmd);
- if(infobyte & 0x01) cmd.note = chunk.ReadUint8();
- if(infobyte & 0x02) cmd.instr = chunk.ReadUint8();
- if(infobyte & 0x04) cmd.vol = chunk.ReadUint8();
- if(infobyte & 0x08) cmd.pan = chunk.ReadUint8();
- if(infobyte & 0x10) cmd.fxcmd = chunk.ReadUint8();
- if(infobyte & 0x20) cmd.fxparam1 = chunk.ReadUint8();
- if(infobyte & 0x40) cmd.fxparam2 = chunk.ReadUint8();
- hasLegacyTempo |= ConvertMT2Command(this, *m, cmd);
- const ModCommand &orig = *m;
- const ROWINDEX fillRows = std::min((uint32)repeatCount, (uint32)numRows - (row + 1));
- for(ROWINDEX r = 0; r < fillRows; r++)
- {
- m += GetNumChannels();
- // cppcheck false-positive
- // cppcheck-suppress selfAssignment
- *m = orig;
- }
- }
- row += repeatCount + 1;
- while(row >= numRows) { row -= numRows; chn++; }
- if(chn >= channelsWithoutDrums) break;
- }
- } else
- {
- for(ROWINDEX row = 0; row < numRows; row++)
- {
- auto rowData = Patterns[pat].GetRow(row);
- for(CHANNELINDEX chn = 0; chn < channelsWithoutDrums; chn++)
- {
- MT2Command cmd;
- chunk.ReadStruct(cmd);
- hasLegacyTempo |= ConvertMT2Command(this, rowData[chn], cmd);
- }
- }
- }
- }
- if(fileHeader.samplesPerTick > 1 && fileHeader.samplesPerTick < 5000)
- {
- if(hasLegacyTempo)
- {
- m_nDefaultTempo.SetRaw(Util::muldivr(110250, TEMPO::fractFact, fileHeader.samplesPerTick));
- m_nTempoMode = TempoMode::Classic;
- } else
- {
- m_nDefaultTempo = TEMPO(44100.0 * 60.0 / (m_nDefaultSpeed * m_nDefaultRowsPerBeat * fileHeader.samplesPerTick));
- m_nTempoMode = TempoMode::Modern;
- }
- }
- // Read extra data
- uint32 numVST = 0;
- std::vector<int8> trackRouting(GetNumChannels(), 0);
- while(extraData.CanRead(8))
- {
- uint32 id = extraData.ReadUint32LE();
- FileReader chunk = extraData.ReadChunk(extraData.ReadUint32LE());
- switch(id)
- {
- case MagicLE("BPM+"):
- if(!hasLegacyTempo)
- {
- m_nTempoMode = TempoMode::Modern;
- double d = chunk.ReadDoubleLE();
- if(d > 0.00000001)
- {
- m_nDefaultTempo = TEMPO(44100.0 * 60.0 / (m_nDefaultSpeed * m_nDefaultRowsPerBeat * d));
- }
- }
- break;
- case MagicLE("TFXM"):
- break;
- case MagicLE("TRKO"):
- break;
- case MagicLE("TRKS"):
- m_nSamplePreAmp = chunk.ReadUint16LE() / 256u; // 131072 is 0dB... I think (that's how MTIOModule_MT2.cpp reads)
- // Dirty workaround for modules that use track automation for a fade-in at the song start (e.g. Rock.mt2)
- if(!m_nSamplePreAmp)
- m_nSamplePreAmp = 48;
- m_nVSTiVolume = m_nSamplePreAmp / 2u;
- for(CHANNELINDEX c = 0; c < GetNumChannels(); c++)
- {
- MT2TrackSettings trackSettings;
- if(chunk.ReadStruct(trackSettings))
- {
- ChnSettings[c].nVolume = static_cast<uint8>(trackSettings.volume >> 10); // 32768 is 0dB
- trackRouting[c] = trackSettings.output;
- }
- }
- break;
- case MagicLE("TRKL"):
- for(CHANNELINDEX i = 0; i < m_nChannels && chunk.CanRead(1); i++)
- {
- std::string name;
- chunk.ReadNullString(name);
- ChnSettings[i].szName = mpt::String::ReadBuf(mpt::String::spacePadded, name.c_str(), name.length());
- }
- break;
- case MagicLE("PATN"):
- for(PATTERNINDEX i = 0; i < fileHeader.numPatterns && chunk.CanRead(1) && Patterns.IsValidIndex(i); i++)
- {
- std::string name;
- chunk.ReadNullString(name);
- Patterns[i].SetName(name);
- }
- break;
- case MagicLE("MSG\0"):
- chunk.Skip(1); // Show message on startup
- m_songMessage.Read(chunk, chunk.BytesLeft(), SongMessage::leCRLF);
- break;
- case MagicLE("PICT"):
- break;
- case MagicLE("SUM\0"):
- {
- uint8 summaryMask[6];
- chunk.ReadArray(summaryMask);
- std::string artist;
- chunk.ReadNullString(artist);
- if(artist != "Unregistered")
- {
- m_songArtist = mpt::ToUnicode(mpt::Charset::Windows1252, artist);
- }
- }
- break;
- case MagicLE("TMAP"):
- break;
- case MagicLE("MIDI"):
- break;
- case MagicLE("TREQ"):
- break;
- case MagicLE("VST2"):
- numVST = chunk.ReadUint32LE();
- #ifdef MPT_WITH_VST
- if(!(loadFlags & loadPluginData))
- {
- break;
- }
- for(uint32 i = 0; i < std::min(numVST, uint32(MAX_MIXPLUGINS)); i++)
- {
- MT2VST vstHeader;
- if(chunk.ReadStruct(vstHeader))
- {
- if(fileHeader.version >= 0x0250)
- chunk.Skip(16 * 4); // Parameter automation map for 16 parameters
- SNDMIXPLUGIN &mixPlug = m_MixPlugins[i];
- mixPlug.Destroy();
- std::string libraryName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, vstHeader.dll);
- mixPlug.Info.szName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, vstHeader.programName);
- if(libraryName.length() > 4 && libraryName[libraryName.length() - 4] == '.')
- {
- // Remove ".dll" from library name
- libraryName.resize(libraryName.length() - 4 );
- }
- mixPlug.Info.szLibraryName = libraryName;
- mixPlug.Info.dwPluginId1 = Vst::kEffectMagic;
- mixPlug.Info.dwPluginId2 = vstHeader.fxID;
- if(vstHeader.track >= m_nChannels)
- {
- mixPlug.SetMasterEffect(true);
- } else
- {
- if(!ChnSettings[vstHeader.track].nMixPlugin)
- {
- ChnSettings[vstHeader.track].nMixPlugin = static_cast<PLUGINDEX>(i + 1);
- } else
- {
- // Channel already has plugin assignment - chain the plugins
- PLUGINDEX outPlug = ChnSettings[vstHeader.track].nMixPlugin - 1;
- while(true)
- {
- if(m_MixPlugins[outPlug].GetOutputPlugin() == PLUGINDEX_INVALID)
- {
- m_MixPlugins[outPlug].SetOutputPlugin(static_cast<PLUGINDEX>(i));
- break;
- }
- outPlug = m_MixPlugins[outPlug].GetOutputPlugin();
- }
- }
- }
- // Read plugin settings
- uint32 dataSize;
- if(vstHeader.useChunks)
- {
- // MT2 only ever calls effGetChunk for programs, and OpenMPT uses the defaultProgram value to determine
- // whether it should use effSetChunk for programs or banks...
- mixPlug.defaultProgram = -1;
- LimitMax(vstHeader.n, std::numeric_limits<decltype(dataSize)>::max() - 4);
- dataSize = vstHeader.n + 4;
- } else
- {
- mixPlug.defaultProgram = vstHeader.programNr;
- LimitMax(vstHeader.n, (std::numeric_limits<decltype(dataSize)>::max() / 4u) - 1);
- dataSize = vstHeader.n * 4 + 4;
- }
- mixPlug.pluginData.resize(dataSize);
- if(vstHeader.useChunks)
- {
- std::memcpy(mixPlug.pluginData.data(), "fEvN", 4); // 'NvEf' plugin data type
- chunk.ReadRaw(mpt::span(mixPlug.pluginData.data() + 4, vstHeader.n));
- } else
- {
- auto memFile = std::make_pair(mpt::as_span(mixPlug.pluginData), mpt::IO::Offset(0));
- mpt::IO::WriteIntLE<uint32>(memFile, 0); // Plugin data type
- for(uint32 param = 0; param < vstHeader.n; param++)
- {
- mpt::IO::Write(memFile, IEEE754binary32LE{chunk.ReadFloatLE()});
- }
- }
- } else
- {
- break;
- }
- }
- #endif // MPT_WITH_VST
- break;
- }
- }
- #ifndef NO_PLUGINS
- // Now that we have both the track settings and plugins, establish the track routing by applying the same plugins to the source track as to the target track:
- for(CHANNELINDEX c = 0; c < GetNumChannels(); c++)
- {
- int8 outTrack = trackRouting[c];
- if(outTrack > c && outTrack < GetNumChannels() && ChnSettings[outTrack].nMixPlugin != 0)
- {
- if(ChnSettings[c].nMixPlugin == 0)
- {
- ChnSettings[c].nMixPlugin = ChnSettings[outTrack].nMixPlugin;
- } else
- {
- PLUGINDEX outPlug = ChnSettings[c].nMixPlugin - 1;
- for(;;)
- {
- if(m_MixPlugins[outPlug].GetOutputPlugin() == PLUGINDEX_INVALID)
- {
- m_MixPlugins[outPlug].SetOutputPlugin(ChnSettings[outTrack].nMixPlugin - 1);
- break;
- }
- outPlug = m_MixPlugins[outPlug].GetOutputPlugin();
- }
- }
- }
- }
- #endif // NO_PLUGINS
- // Read drum channels
- INSTRUMENTINDEX drumMap[8] = { 0 };
- uint16 drumSample[8] = { 0 };
- if(hasDrumChannels)
- {
- MT2DrumsData drumHeader;
- drumData.ReadStruct(drumHeader);
- // Allocate some instruments to handle the drum samples
- for(INSTRUMENTINDEX i = 0; i < 8; i++)
- {
- drumMap[i] = GetNextFreeInstrument(m_nInstruments + 1);
- drumSample[i] = drumHeader.DrumSamples[i];
- if(drumMap[i] != INSTRUMENTINDEX_INVALID)
- {
- ModInstrument *mptIns = AllocateInstrument(drumMap[i], drumHeader.DrumSamples[i] + 1);
- if(mptIns != nullptr)
- {
- mptIns->name = MPT_AFORMAT("Drum #{}")(i+1);
- }
- } else
- {
- drumMap[i] = 0;
- }
- }
- // Get all the drum pattern chunks
- std::vector<FileReader> patternChunks(drumHeader.numDrumPatterns);
- for(uint32 pat = 0; pat < drumHeader.numDrumPatterns; pat++)
- {
- uint16 numRows = file.ReadUint16LE();
- patternChunks[pat] = file.ReadChunk(numRows * 32);
- }
- std::vector<PATTERNINDEX> patMapping(fileHeader.numPatterns, PATTERNINDEX_INVALID);
- for(uint32 ord = 0; ord < fileHeader.numOrders; ord++)
- {
- if(drumHeader.DrumPatternOrder[ord] >= drumHeader.numDrumPatterns || Order()[ord] >= fileHeader.numPatterns)
- continue;
- // Figure out where to write this drum pattern
- PATTERNINDEX writePat = Order()[ord];
- if(patMapping[writePat] == PATTERNINDEX_INVALID)
- {
- patMapping[writePat] = drumHeader.DrumPatternOrder[ord];
- } else if(patMapping[writePat] != drumHeader.DrumPatternOrder[ord])
- {
- // Damn, this pattern has previously used a different drum pattern. Duplicate it...
- PATTERNINDEX newPat = Patterns.Duplicate(writePat);
- if(newPat != PATTERNINDEX_INVALID)
- {
- writePat = newPat;
- Order()[ord] = writePat;
- }
- }
- if(!Patterns.IsValidPat(writePat))
- continue;
- FileReader &chunk = patternChunks[drumHeader.DrumPatternOrder[ord]];
- chunk.Rewind();
- const ROWINDEX numRows = static_cast<ROWINDEX>(chunk.GetLength() / 32u);
- for(ROWINDEX row = 0; row < Patterns[writePat].GetNumRows(); row++)
- {
- ModCommand *m = Patterns[writePat].GetpModCommand(row, m_nChannels - 8);
- for(CHANNELINDEX chn = 0; chn < 8; chn++, m++)
- {
- *m = ModCommand::Empty();
- if(row >= numRows)
- continue;
- uint8 drums[4];
- chunk.ReadArray(drums);
- if(drums[0] & 0x80)
- {
- m->note = NOTE_MIDDLEC;
- m->instr = static_cast<ModCommand::INSTR>(drumMap[chn]);
- uint8 delay = drums[0] & 0x1F;
- if(delay)
- {
- LimitMax(delay, uint8(0x0F));
- m->command = CMD_S3MCMDEX;
- m->param = 0xD0 | delay;
- }
- m->volcmd = VOLCMD_VOLUME;
- // Volume is 0...255, but 128 is equivalent to v64 - we compensate this by halving the global volume of all non-drum instruments
- m->vol = static_cast<ModCommand::VOL>((static_cast<uint16>(drums[1]) + 3) / 4u);
- }
- }
- }
- }
- }
- // Read automation envelopes
- if(fileHeader.flags & MT2FileHeader::automation)
- {
- const uint32 numEnvelopes = ((fileHeader.flags & MT2FileHeader::drumsAutomation) ? m_nChannels : channelsWithoutDrums)
- + ((fileHeader.version >= 0x0250) ? numVST : 0)
- + ((fileHeader.flags & MT2FileHeader::masterAutomation) ? 1 : 0);
- for(uint32 pat = 0; pat < fileHeader.numPatterns; pat++)
- {
- for(uint32 env = 0; env < numEnvelopes && file.CanRead(4); env++)
- {
- // TODO
- ReadMT2Automation(fileHeader.version, file);
- }
- }
- }
- // Read instruments
- std::vector<FileReader> instrChunks(255);
- for(INSTRUMENTINDEX i = 0; i < 255; i++)
- {
- char instrName[32];
- file.ReadArray(instrName);
- uint32 dataLength = file.ReadUint32LE();
- if(dataLength == 32) dataLength += 108 + sizeof(MT2IEnvelope) * 4;
- if(fileHeader.version > 0x0201 && dataLength) dataLength += 4;
- FileReader instrChunk = instrChunks[i] = file.ReadChunk(dataLength);
- ModInstrument *mptIns = nullptr;
- if(i < fileHeader.numInstruments)
- {
- // Default sample assignment if there is no data chunk? Fixes e.g. instrument 33 in Destiny - Dream Alone.mt2
- mptIns = AllocateInstrument(i + 1, i + 1);
- }
- if(mptIns == nullptr)
- continue;
- mptIns->name = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, instrName);
- if(!dataLength)
- continue;
- MT2Instrument insHeader;
- instrChunk.ReadStruct(insHeader);
- uint16 flags = 0;
- if(fileHeader.version >= 0x0201) flags = instrChunk.ReadUint16LE();
- uint32 envMask = MT2Instrument::VolumeEnv | MT2Instrument::PanningEnv;
- if(fileHeader.version >= 0x0202) envMask = instrChunk.ReadUint32LE();
- mptIns->nFadeOut = insHeader.fadeout;
- const NewNoteAction NNA[4] = { NewNoteAction::NoteCut, NewNoteAction::Continue, NewNoteAction::NoteOff, NewNoteAction::NoteFade };
- const DuplicateCheckType DCT[4] = { DuplicateCheckType::None, DuplicateCheckType::Note, DuplicateCheckType::Sample, DuplicateCheckType::Instrument };
- const DuplicateNoteAction DNA[4] = { DuplicateNoteAction::NoteCut, DuplicateNoteAction::NoteFade /* actually continue, but IT doesn't have that */, DuplicateNoteAction::NoteOff, DuplicateNoteAction::NoteFade };
- mptIns->nNNA = NNA[insHeader.nna & 3];
- mptIns->nDCT = DCT[(insHeader.nna >> 8) & 3];
- mptIns->nDNA = DNA[(insHeader.nna >> 12) & 3];
- // Load envelopes
- for(uint32 env = 0; env < 4; env++)
- {
- if(envMask & 1)
- {
- MT2IEnvelope mt2Env;
- instrChunk.ReadStruct(mt2Env);
- const EnvelopeType envType[4] = { ENV_VOLUME, ENV_PANNING, ENV_PITCH, ENV_PITCH };
- InstrumentEnvelope &mptEnv = mptIns->GetEnvelope(envType[env]);
- mptEnv.dwFlags.set(ENV_FILTER, (env == 3) && (mt2Env.flags & 1) != 0);
- mptEnv.dwFlags.set(ENV_ENABLED, (mt2Env.flags & 1) != 0);
- mptEnv.dwFlags.set(ENV_SUSTAIN, (mt2Env.flags & 2) != 0);
- mptEnv.dwFlags.set(ENV_LOOP, (mt2Env.flags & 4) != 0);
- mptEnv.resize(std::min(mt2Env.numPoints.get(), uint8(16)));
- mptEnv.nSustainStart = mptEnv.nSustainEnd = mt2Env.sustainPos;
- mptEnv.nLoopStart = mt2Env.loopStart;
- mptEnv.nLoopEnd = mt2Env.loopEnd;
- for(uint32 p = 0; p < mptEnv.size(); p++)
- {
- mptEnv[p].tick = mt2Env.points[p].x;
- mptEnv[p].value = static_cast<uint8>(Clamp<uint16, uint16>(mt2Env.points[p].y, 0, 64));
- }
- }
- envMask >>= 1;
- }
- if(!mptIns->VolEnv.dwFlags[ENV_ENABLED] && mptIns->nNNA != NewNoteAction::NoteFade)
- {
- mptIns->nFadeOut = int16_max;
- }
- mptIns->SetCutoff(0x7F, true);
- mptIns->SetResonance(0, true);
- if(flags)
- {
- MT2InstrSynth synthData;
- instrChunk.ReadStruct(synthData);
- if(flags & 2)
- {
- mptIns->SetCutoff(FrequencyToCutOff(synthData.cutoff), true);
- mptIns->SetResonance(synthData.resonance, true);
- }
- mptIns->filterMode = synthData.effectID == 1 ? FilterMode::HighPass : FilterMode::LowPass;
- if(flags & 4)
- {
- // VSTi / MIDI synth enabled
- mptIns->nMidiChannel = synthData.midiChannel + 1;
- mptIns->nMixPlug = static_cast<PLUGINDEX>(synthData.device + 1);
- if(synthData.device < 0)
- {
- // TODO: This is a MIDI device - maybe use MIDI I/O plugin to emulate those?
- mptIns->nMidiProgram = synthData.midiProgram + 1; // MT2 only seems to use this for MIDI devices, not VSTis!
- }
- if(synthData.transpose)
- {
- for(uint32 n = 0; n < std::size(mptIns->NoteMap); n++)
- {
- int note = NOTE_MIN + n + synthData.transpose;
- Limit(note, NOTE_MIN, NOTE_MAX);
- mptIns->NoteMap[n] = static_cast<uint8>(note);
- }
- }
- // Instruments with plugin assignments never play samples at the same time!
- mptIns->AssignSample(0);
- }
- }
- }
- // Read sample headers
- std::bitset<256> sampleNoInterpolation;
- std::bitset<256> sampleSynchronized;
- for(SAMPLEINDEX i = 0; i < 256; i++)
- {
- char sampleName[32];
- file.ReadArray(sampleName);
- uint32 dataLength = file.ReadUint32LE();
- FileReader sampleChunk = file.ReadChunk(dataLength);
- if(i < fileHeader.numSamples)
- {
- m_szNames[i + 1] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sampleName);
- }
- if(dataLength && i < fileHeader.numSamples)
- {
- ModSample &mptSmp = Samples[i + 1];
- mptSmp.Initialize(MOD_TYPE_IT);
- mptSmp.SetDefaultCuePoints();
- MT2Sample sampleHeader;
- sampleChunk.ReadStruct(sampleHeader);
- mptSmp.nLength = sampleHeader.length;
- mptSmp.nC5Speed = sampleHeader.frequency;
- if(sampleHeader.depth > 1) { mptSmp.uFlags.set(CHN_16BIT); mptSmp.nLength /= 2u; }
- if(sampleHeader.channels > 1) { mptSmp.uFlags.set(CHN_STEREO); mptSmp.nLength /= 2u; }
- if(sampleHeader.loopType == 1) mptSmp.uFlags.set(CHN_LOOP);
- else if(sampleHeader.loopType == 2) mptSmp.uFlags.set(CHN_LOOP | CHN_PINGPONGLOOP);
- mptSmp.nLoopStart = sampleHeader.loopStart;
- mptSmp.nLoopEnd = sampleHeader.loopEnd;
- mptSmp.nVolume = sampleHeader.volume >> 7;
- if(sampleHeader.panning == -128)
- mptSmp.uFlags.set(CHN_SURROUND);
- else
- mptSmp.nPan = sampleHeader.panning + 128;
- mptSmp.uFlags.set(CHN_PANNING);
- mptSmp.RelativeTone = sampleHeader.note;
- if(sampleHeader.flags & 2)
- {
- // Sample is synchronized to beat
- // The synchronization part is not supported in OpenMPT, but synchronized samples also always play at the same pitch as C-5, which we CAN do!
- sampleSynchronized[i] = true;
- //mptSmp.nC5Speed = Util::muldiv(mptSmp.nC5Speed, sampleHeader.spb, 22050);
- }
- if(sampleHeader.flags & 5)
- {
- // External sample
- mptSmp.uFlags.set(SMP_KEEPONDISK);
- }
- if(sampleHeader.flags & 8)
- {
- sampleNoInterpolation[i] = true;
- for(INSTRUMENTINDEX drum = 0; drum < 8; drum++)
- {
- if(drumSample[drum] == i && Instruments[drumMap[drum]] != nullptr)
- {
- Instruments[drumMap[drum]]->resampling = SRCMODE_NEAREST;
- }
- }
- }
- }
- }
- // Read sample groups
- for(INSTRUMENTINDEX ins = 0; ins < fileHeader.numInstruments; ins++)
- {
- if(instrChunks[ins].GetLength())
- {
- FileReader &chunk = instrChunks[ins];
- MT2Instrument insHeader;
- chunk.Rewind();
- chunk.ReadStruct(insHeader);
- std::vector<MT2Group> groups;
- file.ReadVector(groups, insHeader.numSamples);
- ModInstrument *mptIns = Instruments[ins + 1];
- // Instruments with plugin assignments never play samples at the same time!
- if(mptIns == nullptr || mptIns->nMixPlug != 0)
- continue;
- mptIns->nGlobalVol = 32; // Compensate for extended dynamic range of drum instruments
- mptIns->AssignSample(0);
- for(uint32 note = 0; note < 96; note++)
- {
- if(insHeader.groupMap[note] < insHeader.numSamples)
- {
- const MT2Group &group = groups[insHeader.groupMap[note]];
- SAMPLEINDEX sample = group.sample + 1;
- mptIns->Keyboard[note + 11 + NOTE_MIN] = sample;
- if(sample > 0 && sample <= m_nSamples)
- {
- ModSample &mptSmp = Samples[sample];
- mptSmp.nVibType = static_cast<VibratoType>(insHeader.vibtype & 3); // In fact, MT2 only implements sine vibrato
- mptSmp.nVibSweep = insHeader.vibsweep;
- mptSmp.nVibDepth = insHeader.vibdepth;
- mptSmp.nVibRate = insHeader.vibrate;
- mptSmp.nGlobalVol = uint16(group.vol) * 2;
- mptSmp.nFineTune = group.pitch;
- if(sampleNoInterpolation[sample - 1])
- {
- mptIns->resampling = SRCMODE_NEAREST;
- }
- if(sampleSynchronized[sample - 1])
- {
- mptIns->NoteMap[note + 11 + NOTE_MIN] = NOTE_MIDDLEC;
- }
- }
- // TODO: volume, finetune for duplicated samples
- }
- }
- }
- }
- if(!(loadFlags & loadSampleData))
- return true;
- // Read sample data
- for(SAMPLEINDEX i = 0; i < m_nSamples; i++)
- {
- ModSample &mptSmp = Samples[i + 1];
- mptSmp.Transpose(-(mptSmp.RelativeTone - 49 - (mptSmp.nFineTune / 128.0)) / 12.0);
- mptSmp.nFineTune = 0;
- mptSmp.RelativeTone = 0;
- if(!mptSmp.uFlags[SMP_KEEPONDISK])
- {
- SampleIO(
- mptSmp.uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit,
- mptSmp.uFlags[CHN_STEREO] ? SampleIO::stereoSplit : SampleIO::mono,
- SampleIO::littleEndian,
- SampleIO::MT2)
- .ReadSample(mptSmp, file);
- } else
- {
- // External sample
- const uint32 filenameSize = file.ReadUint32LE();
- file.Skip(12); // Reserved
- std::string filename;
- file.ReadString<mpt::String::maybeNullTerminated>(filename, filenameSize);
- mptSmp.filename = filename;
- #if defined(MPT_EXTERNAL_SAMPLES)
- if(filename.length() >= 2
- && filename[0] != '\\' // Relative path on same drive
- && filename[1] != ':') // Absolute path
- {
- // Relative path in same folder or sub folder
- filename = ".\\" + filename;
- }
- SetSamplePath(i + 1, mpt::PathString::FromLocaleSilent(filename));
- #elif !defined(LIBOPENMPT_BUILD_TEST)
- AddToLog(LogWarning, MPT_UFORMAT("Loading external sample {} ('{}') failed: External samples are not supported.")(i + 1, mpt::ToUnicode(GetCharsetFile(), filename)));
- #endif // MPT_EXTERNAL_SAMPLES
- }
- }
- return true;
- }
- OPENMPT_NAMESPACE_END
|