#include "stdafx.h"
#include "Loaders.h"
#include "ChunkReader.h"
#include "BitReader.h"
OPENMPT_NAMESPACE_BEGIN
struct DMFFileHeader
{
char signature[4]; uint8 version; char tracker[8]; char songname[30];
char composer[20];
uint8 creationDay;
uint8 creationMonth;
uint8 creationYear;
};
MPT_BINARY_STRUCT(DMFFileHeader, 66)
struct DMFChunk
{
enum ChunkIdentifiers
{
idCMSG = MagicLE("CMSG"), idSEQU = MagicLE("SEQU"), idPATT = MagicLE("PATT"), idSMPI = MagicLE("SMPI"), idSMPD = MagicLE("SMPD"), idSMPJ = MagicLE("SMPJ"), idENDE = MagicLE("ENDE"), idSETT = MagicLE("SETT"), };
uint32le id;
uint32le length;
size_t GetLength() const
{
return length;
}
ChunkIdentifiers GetID() const
{
return static_cast<ChunkIdentifiers>(id.get());
}
};
MPT_BINARY_STRUCT(DMFChunk, 8)
struct DMFSequence
{
uint16le loopStart;
uint16le loopEnd;
};
MPT_BINARY_STRUCT(DMFSequence, 4)
struct DMFPatterns
{
uint16le numPatterns; uint8le numTracks; };
MPT_BINARY_STRUCT(DMFPatterns, 3)
struct DMFPatternHeader
{
uint8le numTracks; uint8le beat; uint16le numRows;
uint32le patternLength;
};
MPT_BINARY_STRUCT(DMFPatternHeader, 8)
struct DMFSampleHeader
{
enum SampleFlags
{
smpLoop = 0x01,
smp16Bit = 0x02,
smpCompMask = 0x0C,
smpComp1 = 0x04, smpComp2 = 0x08, smpComp3 = 0x0C, smpLibrary = 0x80, };
uint32le length;
uint32le loopStart;
uint32le loopEnd;
uint16le c3freq; uint8le volume; uint8le flags;
void ConvertToMPT(ModSample &mptSmp) const
{
mptSmp.Initialize();
mptSmp.nLength = length;
mptSmp.nSustainStart = loopStart;
mptSmp.nSustainEnd = loopEnd;
mptSmp.nC5Speed = c3freq;
mptSmp.nGlobalVol = 64;
if(volume)
mptSmp.nVolume = volume + 1;
else
mptSmp.nVolume = 256;
mptSmp.uFlags.set(SMP_NODEFAULTVOLUME, volume == 0);
if((flags & smpLoop) != 0 && mptSmp.nSustainEnd > mptSmp.nSustainStart)
{
mptSmp.uFlags.set(CHN_SUSTAINLOOP);
}
if((flags & smp16Bit) != 0)
{
mptSmp.uFlags.set(CHN_16BIT);
mptSmp.nLength /= 2;
mptSmp.nSustainStart /= 2;
mptSmp.nSustainEnd /= 2;
}
}
};
MPT_BINARY_STRUCT(DMFSampleHeader, 16)
struct DMFSampleHeaderTail
{
uint16le filler;
uint32le crc32;
};
MPT_BINARY_STRUCT(DMFSampleHeaderTail, 6)
struct DMFPatternSettings
{
struct ChannelState
{
ModCommand::NOTE noteBuffer = NOTE_NONE; ModCommand::NOTE lastNote = NOTE_NONE; uint8 vibratoType = 8; uint8 tremoloType = 4; uint8 highOffset = 6; bool playDir = false; };
std::vector<ChannelState> channels; bool realBPMmode = false; uint8 beat = 0; uint8 tempoTicks = 32; uint8 tempoBPM = 120; uint8 internalTicks = 6;
DMFPatternSettings(CHANNELINDEX numChannels)
: channels(numChannels)
{ }
};
static uint8 DMFporta2MPT(uint8 val, const uint8 internalTicks, const bool hasFine)
{
if(val == 0)
return 0;
else if((val <= 0x0F && hasFine) || internalTicks < 2)
return (val | 0xF0);
else
return std::max<uint8>(1, (val / (internalTicks - 1))); }
static uint8 DMFslide2MPT(uint8 val, const uint8 internalTicks, const bool up)
{
val = std::max<uint8>(1, val / 4);
const bool isFine = (val < 0x0F) || (internalTicks < 2);
if(!isFine)
val = std::max<uint8>(1, (val + internalTicks - 2) / (internalTicks - 1));
if(up)
return (isFine ? 0x0F : 0x00) | (val << 4);
else
return (isFine ? 0xF0 : 0x00) | (val & 0x0F);
}
static uint8 DMFtremor2MPT(uint8 val, const uint8 internalTicks)
{
uint8 ontime = (val >> 4);
uint8 offtime = (val & 0x0F);
ontime = static_cast<uint8>(Clamp(ontime * internalTicks / 15, 1, 15));
offtime = static_cast<uint8>(Clamp(offtime * internalTicks / 15, 1, 15));
return (ontime << 4) | offtime;
}
static uint8 DMFdelay2MPT(uint8 val, const uint8 internalTicks)
{
int newval = (int)val * (int)internalTicks / 255;
Limit(newval, 0, 15);
return (uint8)newval;
}
static uint8 DMFvibrato2MPT(uint8 val, const uint8 internalTicks)
{
const int periodInTicks = std::max(1, (val >> 4)) * internalTicks;
const uint8 matchingPeriod = static_cast<uint8>(Clamp((128 / periodInTicks), 1, 15));
return (matchingPeriod << 4) | std::max<uint8>(1, (val & 0x0F));
}
static void ApplyEffectMemory(const ModCommand *m, ROWINDEX row, CHANNELINDEX numChannels, uint8 effect, uint8 ¶m)
{
if(effect == CMD_NONE || param == 0)
{
return;
}
const bool isTonePortaEffect = (effect == CMD_PORTAMENTOUP || effect == CMD_PORTAMENTODOWN || effect == CMD_TONEPORTAMENTO);
const bool isVolSlideEffect = (effect == CMD_VOLUMESLIDE || effect == CMD_TONEPORTAVOL || effect == CMD_VIBRATOVOL);
while(row > 0)
{
m -= numChannels;
row--;
bool isSame = (effect == m->command);
if(isTonePortaEffect && (m->command == CMD_PORTAMENTOUP || m->command == CMD_PORTAMENTODOWN || m->command == CMD_TONEPORTAMENTO))
{
if(m->param < 0xE0)
{
isSame = true;
} else
{
return;
}
} else if(isVolSlideEffect && (m->command == CMD_VOLUMESLIDE || m->command == CMD_TONEPORTAVOL || m->command == CMD_VIBRATOVOL))
{
isSame = true;
}
if(isTonePortaEffect
&& (m->volcmd == VOLCMD_PORTAUP || m->volcmd == VOLCMD_PORTADOWN || m->volcmd == VOLCMD_TONEPORTAMENTO)
&& m->vol != 0)
{
return;
} else if(isVolSlideEffect
&& (m->volcmd == VOLCMD_FINEVOLUP || m->volcmd == VOLCMD_FINEVOLDOWN || m->volcmd == VOLCMD_VOLSLIDEUP || m->volcmd == VOLCMD_VOLSLIDEDOWN)
&& m->vol != 0)
{
return;
}
if(isSame)
{
if(param != m->param && m->param != 0)
{
return;
} else if(param == m->param)
{
param = 0;
return;
}
}
}
}
static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &settings, CSoundFile &sndFile)
{
enum PatternFlags
{
patGlobPack = 0x80, patGlobMask = 0x3F, patCounter = 0x80, patInstr = 0x40, patNote = 0x20, patVolume = 0x10, patInsEff = 0x08, patNoteEff = 0x04, patVolEff = 0x02, };
file.Rewind();
DMFPatternHeader patHead;
file.ReadStruct(patHead);
const ROWINDEX numRows = Clamp(ROWINDEX(patHead.numRows), ROWINDEX(1), MAX_PATTERN_ROWS);
const PATTERNINDEX pat = sndFile.Patterns.InsertAny(numRows);
if(pat == PATTERNINDEX_INVALID)
{
return pat;
}
PatternRow m = sndFile.Patterns[pat].GetRow(0);
const CHANNELINDEX numChannels = std::min<CHANNELINDEX>(sndFile.GetNumChannels() - 1, patHead.numTracks);
for(CHANNELINDEX chn = numChannels + 1; chn < sndFile.GetNumChannels(); chn++)
{
m[chn].note = NOTE_NOTECUT;
}
settings.beat = (patHead.beat >> 4);
bool tempoChange = settings.realBPMmode;
uint8 writeDelay = 0;
std::vector<uint8> channelCounter(numChannels + 1, 0);
for(ROWINDEX row = 0; row < numRows; row++)
{
if(channelCounter[0] == 0)
{
uint8 globalInfo = file.ReadUint8();
if((globalInfo & patGlobPack) != 0)
{
channelCounter[0] = file.ReadUint8();
}
globalInfo &= patGlobMask;
uint8 globalData = 0;
if(globalInfo != 0)
{
globalData = file.ReadUint8();
}
switch(globalInfo)
{
case 1: settings.realBPMmode = false;
settings.tempoTicks = std::max(uint8(1), globalData); settings.tempoBPM = 0; tempoChange = true;
break;
case 2: if(globalData) {
settings.realBPMmode = true;
settings.tempoBPM = globalData; if(settings.beat != 0)
{
settings.tempoTicks = (globalData * settings.beat * 15); }
tempoChange = true;
}
break;
case 3: settings.beat = (globalData >> 4);
if(settings.beat != 0)
{
tempoChange = settings.realBPMmode;
} else
{
settings.realBPMmode = false;
}
break;
case 4: writeDelay = globalData;
break;
case 5: break;
case 6: if(globalData > 0)
{
uint8 &tempoData = (settings.realBPMmode) ? settings.tempoBPM : settings.tempoTicks;
if(tempoData < 256 - globalData)
{
tempoData += globalData;
} else
{
tempoData = 255;
}
tempoChange = true;
}
break;
case 7: if(globalData > 0)
{
uint8 &tempoData = (settings.realBPMmode) ? settings.tempoBPM : settings.tempoTicks;
if(tempoData > 1 + globalData)
{
tempoData -= globalData;
} else
{
tempoData = 1;
}
tempoChange = true;
}
break;
}
} else
{
channelCounter[0]--;
}
int speed = 0, tempo = 0;
if(tempoChange)
{
if(!settings.realBPMmode || settings.beat)
{
const int tickspeed = (settings.realBPMmode) ? std::max(1, settings.tempoBPM * settings.beat * 2) : ((settings.tempoTicks + 1) * 30);
for(speed = 255; speed > 2; speed--)
{
tempo = tickspeed * speed / 48;
if(tempo >= 32 && tempo <= 255)
{
break;
}
}
Limit(tempo, 32, 255);
settings.internalTicks = (uint8)speed;
} else
{
tempoChange = false;
}
}
m = sndFile.Patterns[pat].GetpModCommand(row, 1);
for(CHANNELINDEX chn = 1; chn <= numChannels; chn++, m++)
{
if(channelCounter[chn] == 0)
{
const uint8 channelInfo = file.ReadUint8();
if((channelInfo & patCounter) != 0)
{
channelCounter[chn] = file.ReadUint8();
}
bool slideNote = true; if((channelInfo & patInstr) != 0)
{
m->instr = file.ReadUint8();
if(m->instr != 0)
{
slideNote = false;
}
}
if((channelInfo & patNote) != 0)
{
m->note = file.ReadUint8();
if(m->note >= 1 && m->note <= 108)
{
m->note = static_cast<uint8>(Clamp(m->note + 24, NOTE_MIN, NOTE_MAX));
settings.channels[chn].lastNote = m->note;
} else if(m->note >= 129 && m->note <= 236)
{
m->note = static_cast<uint8>(Clamp((m->note & 0x7F) + 24, NOTE_MIN, NOTE_MAX));
settings.channels[chn].noteBuffer = m->note;
m->note = NOTE_NONE;
} else if(m->note == 255)
{
m->note = NOTE_NOTECUT;
}
}
if(m->note == NOTE_NONE && m->instr > 0)
{
m->note = settings.channels[chn].lastNote;
m->instr = 0;
}
if(m->IsNote())
{
settings.channels[chn].playDir = false;
}
uint8 effect1 = CMD_NONE, effect2 = CMD_NONE, effect3 = CMD_NONE;
uint8 effectParam1 = 0, effectParam2 = 0, effectParam3 = 0;
bool useMem2 = false, useMem3 = false;
if((channelInfo & patVolume) != 0)
{
m->volcmd = VOLCMD_VOLUME;
m->vol = (file.ReadUint8() + 2) / 4; }
if((channelInfo & patInsEff) != 0)
{
effect1 = file.ReadUint8();
effectParam1 = file.ReadUint8();
switch(effect1)
{
case 1: m->note = NOTE_NOTECUT;
effect1 = CMD_NONE;
break;
case 2: m->note = NOTE_KEYOFF;
effect1 = CMD_NONE;
break;
case 3: m->note = settings.channels[chn].lastNote;
settings.channels[chn].playDir = false;
effect1 = CMD_NONE;
break;
case 4: effectParam1 = DMFdelay2MPT(effectParam1, settings.internalTicks);
if(effectParam1)
{
effect1 = CMD_S3MCMDEX;
effectParam1 = 0xD0 | (effectParam1);
} else
{
effect1 = CMD_NONE;
}
if(m->note == NOTE_NONE)
{
m->note = settings.channels[chn].lastNote;
settings.channels[chn].playDir = false;
}
break;
case 5: effectParam1 = std::max(uint8(1), DMFdelay2MPT(effectParam1, settings.internalTicks));
effect1 = CMD_RETRIG;
settings.channels[chn].playDir = false;
break;
case 6: case 7: case 8: case 9: if(row > 0 && effect1 != settings.channels[chn].highOffset)
{
if(sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, (0xA0 | (effect1 - 6))).Row(row - 1).Channel(chn).RetryPreviousRow()))
{
settings.channels[chn].highOffset = effect1;
}
}
effect1 = CMD_OFFSET;
if(m->note == NOTE_NONE)
{
m->note = settings.channels[chn].lastNote;
}
settings.channels[chn].playDir = false;
break;
case 10: effect1 = CMD_S3MCMDEX;
if(settings.channels[chn].playDir == false)
effectParam1 = 0x9F;
else
effectParam1 = 0x9E;
settings.channels[chn].playDir = !settings.channels[chn].playDir;
break;
default:
effect1 = CMD_NONE;
break;
}
}
if((channelInfo & patNoteEff) != 0)
{
effect2 = file.ReadUint8();
effectParam2 = file.ReadUint8();
switch(effect2)
{
case 1: effect2 = static_cast<ModCommand::COMMAND>(effectParam2 < 128 ? CMD_PORTAMENTOUP : CMD_PORTAMENTODOWN);
if(effectParam2 > 128) effectParam2 = 255 - effectParam2 + 1;
effectParam2 = 0xF0 | std::min(uint8(0x0F), effectParam2); break;
case 2: effectParam2 = DMFdelay2MPT(effectParam2, settings.internalTicks);
if(effectParam2)
{
effect2 = CMD_S3MCMDEX;
effectParam2 = 0xD0 | (effectParam2);
} else
{
effect2 = CMD_NONE;
}
useMem2 = true;
break;
case 3: effect2 = CMD_ARPEGGIO;
useMem2 = true;
break;
case 4: case 5: effectParam2 = DMFporta2MPT(effectParam2, settings.internalTicks, true);
effect2 = static_cast<ModCommand::COMMAND>(effect2 == 4 ? CMD_PORTAMENTOUP : CMD_PORTAMENTODOWN);
useMem2 = true;
break;
case 6: if(m->note == NOTE_NONE)
{
m->note = settings.channels[chn].noteBuffer;
}
effectParam2 = DMFporta2MPT(effectParam2, settings.internalTicks, false);
effect2 = CMD_TONEPORTAMENTO;
useMem2 = true;
break;
case 7: m->note = static_cast<ModCommand::NOTE>(Clamp(effectParam2 + 25, NOTE_MIN, NOTE_MAX));
effect2 = CMD_TONEPORTAMENTO;
effectParam2 = 0xFF;
useMem2 = true;
break;
case 8: case 9: case 10: if(row > 0 && effect2 != settings.channels[chn].vibratoType)
{
if(sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, (0x30 | (effect2 - 8))).Row(row - 1).Channel(chn).RetryPreviousRow()))
{
settings.channels[chn].vibratoType = effect2;
}
}
effect2 = CMD_VIBRATO;
effectParam2 = DMFvibrato2MPT(effectParam2, settings.internalTicks);
useMem2 = true;
break;
case 11: effectParam2 = DMFtremor2MPT(effectParam2, settings.internalTicks);
effect2 = CMD_TREMOR;
useMem2 = true;
break;
case 12: effectParam2 = DMFdelay2MPT(effectParam2, settings.internalTicks);
if(effectParam2)
{
effect2 = CMD_S3MCMDEX;
effectParam2 = 0xC0 | (effectParam2);
} else
{
effect2 = CMD_NONE;
m->note = NOTE_NOTECUT;
}
useMem2 = true;
break;
default:
effect2 = CMD_NONE;
break;
}
}
if((channelInfo & patVolEff) != 0)
{
effect3 = file.ReadUint8();
effectParam3 = file.ReadUint8();
switch(effect3)
{
case 1: case 2: effectParam3 = DMFslide2MPT(effectParam3, settings.internalTicks, (effect3 == 1));
effect3 = CMD_VOLUMESLIDE;
useMem3 = true;
break;
case 3: effectParam3 = DMFtremor2MPT(effectParam3, settings.internalTicks);
effect3 = CMD_TREMOR;
useMem3 = true;
break;
case 4: case 5: case 6: if(row > 0 && effect3 != settings.channels[chn].tremoloType)
{
if(sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, (0x40 | (effect3 - 4))).Row(row - 1).Channel(chn).RetryPreviousRow()))
{
settings.channels[chn].tremoloType = effect3;
}
}
effect3 = CMD_TREMOLO;
effectParam3 = DMFvibrato2MPT(effectParam3, settings.internalTicks);
useMem3 = true;
break;
case 7: effect3 = CMD_PANNING8;
break;
case 8: case 9: effectParam3 = DMFslide2MPT(effectParam3, settings.internalTicks, (effect3 == 8));
effect3 = CMD_PANNINGSLIDE;
useMem3 = true;
break;
case 10: effect3 = CMD_PANBRELLO;
effectParam3 = DMFvibrato2MPT(effectParam3, settings.internalTicks);
useMem3 = true;
break;
default:
effect3 = CMD_NONE;
break;
}
}
if(useMem2)
{
ApplyEffectMemory(m, row, sndFile.GetNumChannels(), effect2, effectParam2);
}
if(useMem3)
{
ApplyEffectMemory(m, row, sndFile.GetNumChannels(), effect3, effectParam3);
}
if(slideNote && m->IsNote())
{
if(effect2 == CMD_NONE)
{
effect2 = CMD_TONEPORTAMENTO;
effectParam2 = 0xFF;
} else if(effect3 == CMD_NONE && effect2 != CMD_TONEPORTAMENTO) {
effect3 = CMD_TONEPORTAMENTO;
effectParam3 = 0xFF;
}
}
if(m->volcmd == VOLCMD_VOLUME)
{
if(effect2 == CMD_NONE)
{
effect2 = CMD_VOLUME;
effectParam2 = m->vol;
m->volcmd = VOLCMD_NONE;
} else if(effect3 == CMD_NONE)
{
effect3 = CMD_VOLUME;
effectParam3 = m->vol;
m->volcmd = VOLCMD_NONE;
}
}
ModCommand::TwoRegularCommandsToMPT(effect2, effectParam2, effect3, effectParam3);
if(m->volcmd == VOLCMD_NONE && effect2 != VOLCMD_NONE)
{
m->volcmd = effect2;
m->vol = effectParam2;
}
if(effect1 != CMD_NONE)
{
m->command = effect1;
m->param = effectParam1;
} else if(effect3 != CMD_NONE)
{
m->command = effect3;
m->param = effectParam3;
}
} else
{
channelCounter[chn]--;
}
}
if(tempoChange)
{
tempoChange = false;
sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_TEMPO, static_cast<ModCommand::PARAM>(tempo)).Row(row).Channel(0).RetryNextRow());
sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_SPEED, static_cast<ModCommand::PARAM>(speed)).Row(row).RetryNextRow());
}
if(writeDelay & 0xF0)
{
sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, 0xE0 | (writeDelay >> 4)).Row(row).AllowMultiple());
}
if(writeDelay & 0x0F)
{
const uint8 param = (writeDelay & 0x0F) * settings.internalTicks / 15;
sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, 0x60u | Clamp(param, uint8(1), uint8(15))).Row(row).AllowMultiple());
}
writeDelay = 0;
}
return pat;
}
static bool ValidateHeader(const DMFFileHeader &fileHeader)
{
if(std::memcmp(fileHeader.signature, "DDMF", 4)
|| !fileHeader.version || fileHeader.version > 10)
{
return false;
}
return true;
}
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderDMF(MemoryFileReader file, const uint64 *pfilesize)
{
DMFFileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return ProbeWantMoreData;
}
if(!ValidateHeader(fileHeader))
{
return ProbeFailure;
}
MPT_UNREFERENCED_PARAMETER(pfilesize);
return ProbeSuccess;
}
bool CSoundFile::ReadDMF(FileReader &file, ModLoadingFlags loadFlags)
{
file.Rewind();
DMFFileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return false;
}
if(!ValidateHeader(fileHeader))
{
return false;
}
if(loadFlags == onlyVerifyHeader)
{
return true;
}
InitializeGlobals(MOD_TYPE_DMF);
m_modFormat.formatName = mpt::format(U_("X-Tracker v%1"))(fileHeader.version);
m_modFormat.type = U_("dmf");
m_modFormat.charset = mpt::CharsetCP437;
mpt::String::Read<mpt::String::spacePadded>(m_songName, fileHeader.songname);
{
std::string artist;
mpt::String::Read<mpt::String::spacePadded>(artist, fileHeader.composer);
m_songArtist = mpt::ToUnicode(mpt::CharsetCP437, artist);
}
FileHistory mptHistory;
mptHistory.loadDate.tm_mday = Clamp(fileHeader.creationDay, uint8(1), uint8(31));
mptHistory.loadDate.tm_mon = Clamp(fileHeader.creationMonth, uint8(1), uint8(12)) - 1;
mptHistory.loadDate.tm_year = fileHeader.creationYear;
m_FileHistory.clear();
m_FileHistory.push_back(mptHistory);
ChunkReader chunkFile(file);
ChunkReader::ChunkList<DMFChunk> chunks = chunkFile.ReadChunks<DMFChunk>(1);
FileReader chunk;
DMFSequence seqHeader;
chunk = chunks.GetChunk(DMFChunk::idSEQU);
if(!chunk.ReadStruct(seqHeader))
{
return false;
}
ReadOrderFromFile<uint16le>(Order(), chunk, (chunk.GetLength() - sizeof(DMFSequence)) / 2);
chunk = chunks.GetChunk(DMFChunk::idPATT);
if(chunk.IsValid() && (loadFlags & loadPatternData))
{
DMFPatterns patHeader;
chunk.ReadStruct(patHeader);
m_nChannels = Clamp<uint8, uint8>(patHeader.numTracks, 1, 32) + 1;
std::vector<FileReader> patternChunks(patHeader.numPatterns);
for(auto &patternChunk : patternChunks)
{
DMFPatternHeader header;
chunk.ReadStruct(header);
chunk.SkipBack(sizeof(header));
patternChunk = chunk.ReadChunk(sizeof(header) + header.patternLength);
}
DMFPatternSettings settings(GetNumChannels());
Patterns.ResizeArray(Order().GetLength());
for(PATTERNINDEX &pat : Order())
{
if(pat < patternChunks.size())
{
pat = ConvertDMFPattern(patternChunks[pat], settings, *this);
}
}
if(Order().IsValidPat(seqHeader.loopEnd) && (seqHeader.loopStart > 0 || seqHeader.loopEnd < Order().GetLastIndex()))
{
PATTERNINDEX pat = Order()[seqHeader.loopEnd];
Patterns[pat].WriteEffect(EffectWriter(CMD_POSITIONJUMP, static_cast<ModCommand::PARAM>(seqHeader.loopStart)).Row(Patterns[pat].GetNumRows() - 1).RetryPreviousRow());
}
}
chunk = chunks.GetChunk(DMFChunk::idCMSG);
if(chunk.IsValid())
{
chunk.Skip(1);
m_songMessage.ReadFixedLineLength(chunk, chunk.BytesLeft(), 40, 0);
}
FileReader sampleDataChunk = chunks.GetChunk(DMFChunk::idSMPD);
chunk = chunks.GetChunk(DMFChunk::idSMPI);
m_nSamples = chunk.ReadUint8();
for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
{
chunk.ReadSizedString<uint8le, mpt::String::spacePadded>(m_szNames[smp]);
DMFSampleHeader sampleHeader;
ModSample &sample = Samples[smp];
chunk.ReadStruct(sampleHeader);
sampleHeader.ConvertToMPT(sample);
if(fileHeader.version >= 8)
{
chunk.ReadString<mpt::String::spacePadded>(sample.filename, 8);
}
chunk.Skip(sizeof(DMFSampleHeaderTail));
FileReader sampleData = sampleDataChunk.ReadChunk(sampleDataChunk.ReadUint32LE());
if(sampleData.IsValid() && (loadFlags & loadSampleData))
{
SampleIO(
sample.uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit,
SampleIO::mono,
SampleIO::littleEndian,
(sampleHeader.flags & DMFSampleHeader::smpCompMask) == DMFSampleHeader::smpComp1 ? SampleIO::DMF : SampleIO::signedPCM)
.ReadSample(sample, sampleData);
}
}
InitializeChannels();
m_SongFlags = SONG_LINEARSLIDES | SONG_ITCOMPATGXX; m_nDefaultSpeed = 6;
m_nDefaultTempo.Set(120);
m_nDefaultGlobalVolume = 256;
m_nSamplePreAmp = m_nVSTiVolume = 48;
return true;
}
struct DMFHNode
{
int16 left, right;
uint8 value;
};
struct DMFHTree
{
BitReader file;
int lastnode, nodecount;
DMFHNode nodes[256];
DMFHTree(FileReader &file)
: file(file)
, lastnode(0)
, nodecount(0)
{
MemsetZero(nodes);
}
void DMFNewNode()
{
int actnode = nodecount;
if(actnode > 255) return;
nodes[actnode].value = static_cast<uint8>(file.ReadBits(7));
bool isLeft = file.ReadBits(1) != 0;
bool isRight = file.ReadBits(1) != 0;
actnode = lastnode;
if(actnode > 255) return;
nodecount++;
lastnode = nodecount;
if(isLeft)
{
nodes[actnode].left = (int16)lastnode;
DMFNewNode();
} else
{
nodes[actnode].left = -1;
}
lastnode = nodecount;
if(isRight)
{
nodes[actnode].right = (int16)lastnode;
DMFNewNode();
} else
{
nodes[actnode].right = -1;
}
}
};
uintptr_t DMFUnpack(FileReader &file, uint8 *psample, uint32 maxlen)
{
DMFHTree tree(file);
uint8 value = 0, delta = 0;
try
{
tree.DMFNewNode();
for(uint32 i = 0; i < maxlen; i++)
{
int actnode = 0;
bool sign = tree.file.ReadBits(1) != 0;
do
{
if(tree.file.ReadBits(1))
actnode = tree.nodes[actnode].right;
else
actnode = tree.nodes[actnode].left;
if(actnode > 255) break;
delta = tree.nodes[actnode].value;
} while((tree.nodes[actnode].left >= 0) && (tree.nodes[actnode].right >= 0));
if(sign) delta ^= 0xFF;
value += delta;
psample[i] = value;
}
} catch(const BitReader::eof &)
{
}
return tree.file.GetPosition();
}
OPENMPT_NAMESPACE_END