#include "stdafx.h"
#include "Loaders.h"
#include "ChunkReader.h"
OPENMPT_NAMESPACE_BEGIN
struct MDLFileHeader
{
char id[4]; uint8 version;
};
MPT_BINARY_STRUCT(MDLFileHeader, 5)
struct MDLChunk
{
enum ChunkIdentifiers
{
idInfo = MagicLE("IN"),
idMessage = MagicLE("ME"),
idPats = MagicLE("PA"),
idPatNames = MagicLE("PN"),
idTracks = MagicLE("TR"),
idInstrs = MagicLE("II"),
idVolEnvs = MagicLE("VE"),
idPanEnvs = MagicLE("PE"),
idFreqEnvs = MagicLE("FE"),
idSampleInfo = MagicLE("IS"),
ifSampleData = MagicLE("SA"),
};
uint16le id;
uint32le length;
size_t GetLength() const
{
return length;
}
ChunkIdentifiers GetID() const
{
return static_cast<ChunkIdentifiers>(id.get());
}
};
MPT_BINARY_STRUCT(MDLChunk, 6)
struct MDLInfoBlock
{
char title[32];
char composer[20];
uint16le numOrders;
uint16le restartPos;
uint8le globalVol; uint8le speed; uint8le tempo; uint8le chnSetup[32];
};
MPT_BINARY_STRUCT(MDLInfoBlock, 91)
struct MDLSampleHeader
{
uint8le smpNum;
uint8le lastNote;
uint8le volume;
uint8le volEnvFlags; uint8le panning;
uint8le panEnvFlags;
uint16le fadeout;
uint8le vibSpeed;
uint8le vibDepth;
uint8le vibSweep;
uint8le vibType;
uint8le reserved; uint8le freqEnvFlags;
};
MPT_BINARY_STRUCT(MDLSampleHeader, 14)
struct MDLEnvelope
{
uint8 envNum;
struct
{
uint8 x; uint8 y; } nodes[15];
uint8 flags;
uint8 loop;
void ConvertToMPT(InstrumentEnvelope &mptEnv) const
{
mptEnv.dwFlags.reset();
mptEnv.clear();
mptEnv.reserve(15);
int16 tick = -nodes[0].x;
for(uint8 n = 0; n < 15; n++)
{
if(!nodes[n].x)
break;
tick += nodes[n].x;
mptEnv.push_back(EnvelopeNode(tick, std::min(nodes[n].y, uint8(64)))); }
mptEnv.nLoopStart = (loop & 0x0F);
mptEnv.nLoopEnd = (loop >> 4);
mptEnv.nSustainStart = mptEnv.nSustainEnd = (flags & 0x0F);
if(flags & 0x10) mptEnv.dwFlags.set(ENV_SUSTAIN);
if(flags & 0x20) mptEnv.dwFlags.set(ENV_LOOP);
}
};
MPT_BINARY_STRUCT(MDLEnvelope, 33)
struct MDLPatternHeader
{
uint8le channels;
uint8le lastRow;
char name[16];
};
MPT_BINARY_STRUCT(MDLPatternHeader, 18)
enum
{
MDLNOTE_NOTE = 1 << 0,
MDLNOTE_SAMPLE = 1 << 1,
MDLNOTE_VOLUME = 1 << 2,
MDLNOTE_EFFECTS = 1 << 3,
MDLNOTE_PARAM1 = 1 << 4,
MDLNOTE_PARAM2 = 1 << 5,
};
static const VibratoType MDLVibratoType[] = { VIB_SINE, VIB_RAMP_DOWN, VIB_SQUARE, VIB_SINE };
static const ModCommand::COMMAND MDLEffTrans[] =
{
CMD_NONE,
CMD_PORTAMENTOUP,
CMD_PORTAMENTODOWN,
CMD_TONEPORTAMENTO,
CMD_VIBRATO,
CMD_ARPEGGIO,
CMD_NONE,
CMD_TEMPO,
CMD_PANNING8,
CMD_SETENVPOSITION,
CMD_NONE,
CMD_POSITIONJUMP,
CMD_GLOBALVOLUME,
CMD_PATTERNBREAK,
CMD_S3MCMDEX,
CMD_SPEED,
CMD_VOLUMESLIDE, CMD_VOLUMESLIDE, CMD_RETRIG,
CMD_TREMOLO,
CMD_TREMOR,
CMD_NONE,
};
static void ConvertMDLCommand(uint8_t &cmd, uint8_t ¶m)
{
if(cmd >= CountOf(MDLEffTrans))
return;
uint8 origCmd = cmd;
cmd = MDLEffTrans[cmd];
switch(origCmd)
{
#ifdef MODPLUG_TRACKER
case 0x07: param = std::max(param, uint8(0x20));
break;
#endif case 0x08: param = (param & 0x7F) * 2u;
break;
case 0x0C: param = (param + 1) / 2u;
break;
case 0x0D: param = 10 * (param >> 4) + (param & 0x0F);
break;
case 0x0E: switch(param >> 4)
{
case 0x0: case 0x3: case 0x5: case 0x8: cmd = CMD_NONE;
break;
case 0x1: cmd = CMD_PANNINGSLIDE;
param = (std::min<uint8>(param & 0x0F, 0x0E) << 4) | 0x0F;
break;
case 0x2: cmd = CMD_PANNINGSLIDE;
param = 0xF0 | std::min<uint8>(param & 0x0F, 0x0E);
break;
case 0x4: param = 0x30 | (param & 0x0F);
break;
case 0x6: param = 0xB0 | (param & 0x0F);
break;
case 0x7: param = 0x40 | (param & 0x0F);
break;
case 0x9: cmd = CMD_RETRIG;
param &= 0x0F;
break;
case 0xA: cmd = CMD_GLOBALVOLSLIDE;
param = 0xF0 & (((param & 0x0F) + 1) << 3);
break;
case 0xB: cmd = CMD_GLOBALVOLSLIDE;
param = ((param & 0x0F) + 1) >> 1;
break;
case 0xC: case 0xD: case 0xE: break;
case 0xF: cmd = CMD_OFFSET;
break;
}
break;
case 0x10: if(param < 0xE0)
{
param >>= 2;
if(param > 0x0F)
param = 0x0F;
param <<= 4;
} else if(param < 0xF0)
{
param = (((param & 0x0F) << 2) | 0x0F);
} else
{
param = ((param << 4) | 0x0F);
}
break;
case 0x11: if(param < 0xE0)
{
param >>= 2;
if(param > 0x0F)
param = 0x0F;
} else if(param < 0xF0)
{
param = (((param & 0x0F) >> 2) | 0xF0);
} else
{
}
break;
}
}
static bool ImportMDLCommands(ModCommand &m, uint8 vol, uint8 e1, uint8 e2, uint8 p1, uint8 p2)
{
if(e2 >= 1 && e2 <= 6)
e2 += 15;
ConvertMDLCommand(e1, p1);
ConvertMDLCommand(e2, p2);
if(e1 == CMD_OFFSET)
{
p1 = (p1 & 0x0F) ? 0xFF : p2;
if(e2 == CMD_OFFSET)
e2 = CMD_NONE;
} else if (e2 == CMD_OFFSET)
{
p2 = (p2 & 0x0F) ? 0xFF : 0;
}
if(vol)
{
m.volcmd = VOLCMD_VOLUME;
m.vol = (vol + 2) / 4u;
}
ModCommand::CombineEffects(e1, p1, e2, p2);
bool lostCommand = false;
if(e1 == CMD_NONE)
{
} else if(e2 == CMD_NONE)
{
e2 = e1;
p2 = p1;
e1 = CMD_NONE;
} else if(e1 == e2 && e1 != CMD_S3MCMDEX)
{
e1 = CMD_NONE;
} else if(!vol)
{
lostCommand |= !ModCommand::TwoRegularCommandsToMPT(e1, p1, e2, p2);
m.volcmd = e1;
m.vol = p1;
} else
{
if(ModCommand::GetEffectWeight((ModCommand::COMMAND)e1) > ModCommand::GetEffectWeight((ModCommand::COMMAND)e2))
{
std::swap(e1, e2);
std::swap(p1, p2);
}
}
m.command = e2;
m.param = p2;
return lostCommand;
}
static void MDLReadEnvelopes(FileReader file, std::vector<MDLEnvelope> &envelopes)
{
if(!file.CanRead(1))
return;
envelopes.resize(64);
uint8 numEnvs = file.ReadUint8();
while(numEnvs--)
{
MDLEnvelope mdlEnv;
if(!file.ReadStruct(mdlEnv) || mdlEnv.envNum > 63)
continue;
envelopes[mdlEnv.envNum] = mdlEnv;
}
}
static void CopyEnvelope(InstrumentEnvelope &mptEnv, uint8 flags, std::vector<MDLEnvelope> &envelopes)
{
uint8 envNum = flags & 0x3F;
if(envNum < envelopes.size())
envelopes[envNum].ConvertToMPT(mptEnv);
mptEnv.dwFlags.set(ENV_ENABLED, (flags & 0x80) && !mptEnv.empty());
}
static bool ValidateHeader(const MDLFileHeader &fileHeader)
{
if(std::memcmp(fileHeader.id, "DMDL", 4)
|| fileHeader.version >= 0x20)
{
return false;
}
return true;
}
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderMDL(MemoryFileReader file, const uint64 *pfilesize)
{
MDLFileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return ProbeWantMoreData;
}
if(!ValidateHeader(fileHeader))
{
return ProbeFailure;
}
MPT_UNREFERENCED_PARAMETER(pfilesize);
return ProbeSuccess;
}
bool CSoundFile::ReadMDL(FileReader &file, ModLoadingFlags loadFlags)
{
file.Rewind();
MDLFileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return false;
}
if(!ValidateHeader(fileHeader))
{
return false;
}
if(loadFlags == onlyVerifyHeader)
{
return true;
}
ChunkReader chunkFile(file);
ChunkReader::ChunkList<MDLChunk> chunks = chunkFile.ReadChunks<MDLChunk>(0);
FileReader chunk = chunks.GetChunk(MDLChunk::idInfo);
MDLInfoBlock info;
if(!chunk.IsValid() || !chunk.ReadStruct(info))
{
return false;
}
InitializeGlobals(MOD_TYPE_MDL);
m_SongFlags = SONG_ITCOMPATGXX;
m_playBehaviour.set(kPerChannelGlobalVolSlide);
m_playBehaviour.reset(kITVibratoTremoloPanbrello);
m_playBehaviour.reset(kITSCxStopsSample);
m_modFormat.formatName = U_("Digitrakker");
m_modFormat.type = U_("mdl");
m_modFormat.madeWithTracker = U_("Digitrakker ") + (
(fileHeader.version == 0x11) ? U_("3") : (fileHeader.version == 0x10) ? U_("2.3")
: (fileHeader.version == 0x00) ? U_("2.0 - 2.2b") : U_(""));
m_modFormat.charset = mpt::CharsetCP437;
mpt::String::Read<mpt::String::spacePadded>(m_songName, info.title);
{
std::string artist;
mpt::String::Read<mpt::String::spacePadded>(artist, info.composer);
m_songArtist = mpt::ToUnicode(mpt::CharsetCP437, artist);
}
m_nDefaultGlobalVolume = info.globalVol + 1;
m_nDefaultSpeed = Clamp<uint8, uint8>(info.speed, 1, 255);
m_nDefaultTempo.Set(Clamp<uint8, uint8>(info.tempo, 4, 255));
ReadOrderFromFile<uint8>(Order(), chunk, info.numOrders);
Order().SetRestartPos(info.restartPos);
m_nChannels = 0;
for(CHANNELINDEX c = 0; c < 32; c++)
{
ChnSettings[c].Reset();
ChnSettings[c].nPan = (info.chnSetup[c] & 0x7F) * 2u;
if(ChnSettings[c].nPan == 254)
ChnSettings[c].nPan = 256;
if(info.chnSetup[c] & 0x80)
ChnSettings[c].dwFlags.set(CHN_MUTE);
else
m_nChannels = c + 1;
chunk.ReadString<mpt::String::spacePadded>(ChnSettings[c].szName, 8);
}
chunk = chunks.GetChunk(MDLChunk::idMessage);
m_songMessage.Read(chunk, chunk.GetLength(), SongMessage::leCR);
chunk = chunks.GetChunk(MDLChunk::idSampleInfo);
if(chunk.IsValid())
{
FileReader dataChunk = chunks.GetChunk(MDLChunk::ifSampleData);
uint8 numSamples = chunk.ReadUint8();
for(uint8 smp = 0; smp < numSamples; smp++)
{
const SAMPLEINDEX sampleIndex = chunk.ReadUint8();
if(sampleIndex == 0 || sampleIndex >= MAX_SAMPLES || !chunk.CanRead(32 + 8 + 2 + 12 + 2))
break;
if(sampleIndex > GetNumSamples())
m_nSamples = sampleIndex;
ModSample &sample = Samples[sampleIndex];
sample.Initialize();
chunk.ReadString<mpt::String::spacePadded>(m_szNames[sampleIndex], 32);
chunk.ReadString<mpt::String::spacePadded>(sample.filename, 8);
uint32 c4speed;
if(fileHeader.version < 0x10)
c4speed = chunk.ReadUint16LE();
else
c4speed = chunk.ReadUint32LE();
sample.nC5Speed = c4speed * 2u;
sample.nLength = chunk.ReadUint32LE();
sample.nLoopStart = chunk.ReadUint32LE();
sample.nLoopEnd = chunk.ReadUint32LE();
if(sample.nLoopEnd != 0)
{
sample.uFlags.set(CHN_LOOP);
sample.nLoopEnd += sample.nLoopStart;
}
uint8 volume = chunk.ReadUint8();
if(fileHeader.version < 0x10)
sample.nVolume = volume;
uint8 flags = chunk.ReadUint8();
if(flags & 0x01)
{
sample.uFlags.set(CHN_16BIT);
sample.nLength /= 2u;
sample.nLoopStart /= 2u;
sample.nLoopEnd /= 2u;
}
sample.uFlags.set(CHN_PINGPONGLOOP, (flags & 0x02) != 0);
SampleIO sampleIO(
(flags & 0x01) ? SampleIO::_16bit : SampleIO::_8bit,
SampleIO::mono,
SampleIO::littleEndian,
(flags & 0x0C) ? SampleIO::MDL : SampleIO::signedPCM);
if(loadFlags & loadSampleData)
{
sampleIO.ReadSample(sample, dataChunk);
}
}
}
chunk = chunks.GetChunk(MDLChunk::idInstrs);
if(chunk.IsValid())
{
std::vector<MDLEnvelope> volEnvs, panEnvs, pitchEnvs;
MDLReadEnvelopes(chunks.GetChunk(MDLChunk::idVolEnvs), volEnvs);
MDLReadEnvelopes(chunks.GetChunk(MDLChunk::idPanEnvs), panEnvs);
MDLReadEnvelopes(chunks.GetChunk(MDLChunk::idFreqEnvs), pitchEnvs);
uint8 numInstruments = chunk.ReadUint8();
for(uint8 i = 0; i < numInstruments; i++)
{
uint8 ins = chunk.ReadUint8();
uint8 numSamples = chunk.ReadUint8();
uint8 firstNote = 0;
ModInstrument *mptIns = nullptr;
if(ins == 0
|| !chunk.CanRead(32 + sizeof(MDLSampleHeader) * numSamples)
|| (mptIns = AllocateInstrument(ins)) == nullptr)
{
chunk.Skip(32 + sizeof(MDLSampleHeader) * numSamples);
continue;
}
chunk.ReadString<mpt::String::spacePadded>(mptIns->name, 32);
while(numSamples--)
{
MDLSampleHeader sampleHeader;
chunk.ReadStruct(sampleHeader);
if(sampleHeader.smpNum == 0)
continue;
#if 1
#if MPT_GCC_BEFORE(6,1,0)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wtype-limits"
#endif
STATIC_ASSERT((mpt::limits<decltype(sampleHeader.smpNum)>::max)() < MAX_SAMPLES);
#if MPT_GCC_BEFORE(6,1,0)
#pragma GCC diagnostic pop
#endif
#else #endif
LimitMax(sampleHeader.lastNote, static_cast<uint8>(CountOf(mptIns->Keyboard)));
for(uint8 n = firstNote; n <= sampleHeader.lastNote; n++)
{
mptIns->Keyboard[n] = sampleHeader.smpNum;
}
firstNote = sampleHeader.lastNote + 1;
CopyEnvelope(mptIns->VolEnv, sampleHeader.volEnvFlags, volEnvs);
CopyEnvelope(mptIns->PanEnv, sampleHeader.panEnvFlags, panEnvs);
CopyEnvelope(mptIns->PitchEnv, sampleHeader.freqEnvFlags, pitchEnvs);
mptIns->nFadeOut = (sampleHeader.fadeout + 1u) / 2u;
#ifdef MODPLUG_TRACKER
if((mptIns->VolEnv.dwFlags & (ENV_ENABLED | ENV_LOOP)) == ENV_ENABLED)
{
mptIns->VolEnv.nLoopStart = mptIns->VolEnv.nLoopEnd = static_cast<uint8>(mptIns->VolEnv.size() - 1);
mptIns->VolEnv.dwFlags.set(ENV_LOOP);
}
for(auto &p : mptIns->PitchEnv)
{
p.value = (p.value * 6u) / 16u;
}
#endif
ModSample &mptSmp = Samples[sampleHeader.smpNum];
if(sampleHeader.volEnvFlags & 0x40)
mptSmp.nVolume = sampleHeader.volume;
else
mptSmp.uFlags.set(SMP_NODEFAULTVOLUME);
mptSmp.nPan = std::min<uint16>(sampleHeader.panning * 2, 254);
mptSmp.nVibType = MDLVibratoType[sampleHeader.vibType & 3];
mptSmp.nVibSweep = sampleHeader.vibSweep;
mptSmp.nVibDepth = sampleHeader.vibDepth;
mptSmp.nVibRate = sampleHeader.vibSpeed;
if(sampleHeader.panEnvFlags & 0x40)
mptSmp.uFlags.set(CHN_PANNING);
}
}
}
std::vector<FileReader> tracks;
if((loadFlags & loadPatternData) && (chunk = chunks.GetChunk(MDLChunk::idTracks)).IsValid())
{
uint32 numTracks = chunk.ReadUint16LE();
tracks.resize(numTracks + 1);
for(uint32 i = 1; i <= numTracks; i++)
{
tracks[i] = chunk.ReadChunk(chunk.ReadUint16LE());
}
}
if((loadFlags & loadPatternData) && (chunk = chunks.GetChunk(MDLChunk::idPats)).IsValid())
{
PATTERNINDEX numPats = chunk.ReadUint8();
for(PATTERNINDEX pat = 0; pat < numPats; pat++)
{
CHANNELINDEX numChans = 32;
if(fileHeader.version >= 0x10)
{
MDLPatternHeader patHead;
chunk.ReadStruct(patHead);
if(patHead.channels > m_nChannels && patHead.channels <= 32)
m_nChannels = patHead.channels;
numChans = patHead.channels;
}
for(CHANNELINDEX chn = 0; chn < numChans; chn++)
{
if(chunk.ReadUint16LE() > 0 && chn >= m_nChannels)
m_nChannels = chn + 1;
}
}
chunk.Seek(1);
Patterns.ResizeArray(numPats);
for(PATTERNINDEX pat = 0; pat < numPats; pat++)
{
CHANNELINDEX numChans = 32;
ROWINDEX numRows = 64;
char name[17] = "";
if(fileHeader.version >= 0x10)
{
MDLPatternHeader patHead;
chunk.ReadStruct(patHead);
numChans = patHead.channels;
numRows = patHead.lastRow + 1;
mpt::String::Read<mpt::String::spacePadded>(name, patHead.name);
}
if(!Patterns.Insert(pat, numRows))
{
chunk.Skip(2 * numChans);
continue;
}
Patterns[pat].SetName(name);
for(CHANNELINDEX chn = 0; chn < numChans; chn++)
{
uint16 trkNum = chunk.ReadUint16LE();
if(!trkNum || trkNum >= tracks.size() || chn >= m_nChannels)
continue;
FileReader &track = tracks[trkNum];
track.Rewind();
ROWINDEX row = 0;
while(row < numRows && track.CanRead(1))
{
ModCommand *m = Patterns[pat].GetpModCommand(row, chn);
uint8 b = track.ReadUint8();
uint8 x = (b >> 2), y = (b & 3);
switch(y)
{
case 0:
row += x + 1;
break;
case 1:
if(row > 0)
{
ModCommand &orig = *Patterns[pat].GetpModCommand(row - 1, chn);
do
{
*m = orig;
m += m_nChannels;
row++;
} while (row < numRows && x--);
}
break;
case 2:
if(row > x)
{
*m = *Patterns[pat].GetpModCommand(x, chn);
}
row++;
break;
case 3:
if(x & MDLNOTE_NOTE)
{
b = track.ReadUint8();
m->note = (b > 120) ? NOTE_KEYOFF : b;
}
if(x & MDLNOTE_SAMPLE)
{
m->instr = track.ReadUint8();
}
{
uint8 vol = 0, e1 = 0, e2 = 0, p1 = 0, p2 = 0;
if(x & MDLNOTE_VOLUME)
{
vol = track.ReadUint8();
}
if(x & MDLNOTE_EFFECTS)
{
b = track.ReadUint8();
e1 = (b & 0x0F);
e2 = (b >> 4);
}
if(x & MDLNOTE_PARAM1)
p1 = track.ReadUint8();
if(x & MDLNOTE_PARAM2)
p2 = track.ReadUint8();
ImportMDLCommands(*m, vol, e1, e2, p1, p2);
}
row++;
break;
}
}
}
}
}
if((loadFlags & loadPatternData) && (chunk = chunks.GetChunk(MDLChunk::idPatNames)).IsValid())
{
PATTERNINDEX i = 0;
while(i < Patterns.Size() && chunk.CanRead(16))
{
char name[17];
chunk.ReadString<mpt::String::spacePadded>(name, 16);
Patterns[i].SetName(name);
}
}
return true;
}
OPENMPT_NAMESPACE_END