#include "stdafx.h"
#include "Loaders.h"
OPENMPT_NAMESPACE_BEGIN
struct IMFChannel
{
char name[12]; uint8 chorus; uint8 reverb; uint8 panning; uint8 status; };
MPT_BINARY_STRUCT(IMFChannel, 16)
struct IMFFileHeader
{
enum SongFlags
{
linearSlides = 0x01,
};
char title[32]; uint16le ordNum; uint16le patNum; uint16le insNum; uint16le flags; uint8le unused1[8];
uint8le tempo; uint8le bpm; uint8le master; uint8le amp; uint8le unused2[8];
char im10[4]; IMFChannel channels[32]; };
MPT_BINARY_STRUCT(IMFFileHeader, 576)
struct IMFEnvelope
{
enum EnvFlags
{
envEnabled = 0x01,
envSustain = 0x02,
envLoop = 0x04,
};
uint8 points; uint8 sustain; uint8 loopStart; uint8 loopEnd; uint8 flags; uint8 unused[3];
};
MPT_BINARY_STRUCT(IMFEnvelope, 8)
struct IMFEnvNode
{
uint16le tick;
uint16le value;
};
MPT_BINARY_STRUCT(IMFEnvNode, 4)
struct IMFInstrument
{
enum EnvTypes
{
volEnv = 0,
panEnv = 1,
filterEnv = 2,
};
char name[32]; uint8le map[120]; uint8le unused[8];
IMFEnvNode nodes[3][16];
IMFEnvelope env[3];
uint16le fadeout; uint16le smpNum; char ii10[4];
void ConvertEnvelope(InstrumentEnvelope &mptEnv, EnvTypes e) const
{
const int shift = (e == volEnv) ? 0 : 2;
mptEnv.dwFlags.set(ENV_ENABLED, (env[e].flags & 1) != 0);
mptEnv.dwFlags.set(ENV_SUSTAIN, (env[e].flags & 2) != 0);
mptEnv.dwFlags.set(ENV_LOOP, (env[e].flags & 4) != 0);
mptEnv.resize(Clamp(env[e].points, uint8(2), uint8(16)));
mptEnv.nLoopStart = env[e].loopStart;
mptEnv.nLoopEnd = env[e].loopEnd;
mptEnv.nSustainStart = mptEnv.nSustainEnd = env[e].sustain;
uint16 minTick = 0; for(uint32 n = 0; n < mptEnv.size(); n++)
{
minTick = mptEnv[n].tick = std::max<uint16>(minTick, nodes[e][n].tick);
minTick++;
mptEnv[n].value = static_cast<uint8>(std::min(nodes[e][n].value >> shift, ENVELOPE_MAX));
}
}
void ConvertToMPT(ModInstrument &mptIns, SAMPLEINDEX firstSample) const
{
mpt::String::Read<mpt::String::nullTerminated>(mptIns.name, name);
if(smpNum)
{
STATIC_ASSERT(CountOf(mptIns.Keyboard) >= CountOf(map));
for(size_t note = 0; note < CountOf(map); note++)
{
mptIns.Keyboard[note] = firstSample + map[note];
}
}
mptIns.nFadeOut = fadeout;
ConvertEnvelope(mptIns.VolEnv, volEnv);
ConvertEnvelope(mptIns.PanEnv, panEnv);
ConvertEnvelope(mptIns.PitchEnv, filterEnv);
if(mptIns.PitchEnv.dwFlags[ENV_ENABLED])
mptIns.PitchEnv.dwFlags.set(ENV_FILTER);
if(!mptIns.VolEnv.dwFlags[ENV_ENABLED] && !mptIns.nFadeOut)
mptIns.nFadeOut = 32767;
}
};
MPT_BINARY_STRUCT(IMFInstrument, 384)
struct IMFSample
{
enum SampleFlags
{
smpLoop = 0x01,
smpPingPongLoop = 0x02,
smp16Bit = 0x04,
smpPanning = 0x08,
};
char filename[13]; uint8le unused1[3];
uint32le length; uint32le loopStart; uint32le loopEnd; uint32le c5Speed; uint8le volume; uint8le panning; uint8le unused2[14];
uint8le flags; uint8le unused3[5];
uint16le ems; uint32le dram; char is10[4];
void ConvertToMPT(ModSample &mptSmp) const
{
mptSmp.Initialize();
mpt::String::Read<mpt::String::nullTerminated>(mptSmp.filename, filename);
mptSmp.nLength = length;
mptSmp.nLoopStart = loopStart;
mptSmp.nLoopEnd = loopEnd;
mptSmp.nC5Speed = c5Speed;
mptSmp.nVolume = volume * 4;
mptSmp.nPan = panning;
if(flags & smpLoop)
mptSmp.uFlags.set(CHN_LOOP);
if(flags & smpPingPongLoop)
mptSmp.uFlags.set(CHN_PINGPONGLOOP);
if(flags & smp16Bit)
{
mptSmp.uFlags.set(CHN_16BIT);
mptSmp.nLength /= 2;
mptSmp.nLoopStart /= 2;
mptSmp.nLoopEnd /= 2;
}
if(flags & smpPanning)
mptSmp.uFlags.set(CHN_PANNING);
}
};
MPT_BINARY_STRUCT(IMFSample, 64)
static const EffectCommand imfEffects[] =
{
CMD_NONE,
CMD_SPEED, CMD_TEMPO, CMD_TONEPORTAMENTO, CMD_TONEPORTAVOL, CMD_VIBRATO, CMD_VIBRATOVOL, CMD_FINEVIBRATO, CMD_TREMOLO, CMD_ARPEGGIO, CMD_PANNING8, CMD_PANNINGSLIDE, CMD_VOLUME, CMD_VOLUMESLIDE, CMD_VOLUMESLIDE, CMD_S3MCMDEX, CMD_NOTESLIDEUP, CMD_NOTESLIDEDOWN, CMD_PORTAMENTOUP, CMD_PORTAMENTODOWN, CMD_PORTAMENTOUP, CMD_PORTAMENTODOWN, CMD_MIDI, CMD_NONE, CMD_OFFSET, CMD_NONE, CMD_KEYOFF, CMD_RETRIG, CMD_TREMOR, CMD_POSITIONJUMP, CMD_PATTERNBREAK, CMD_GLOBALVOLUME, CMD_GLOBALVOLSLIDE, CMD_S3MCMDEX, CMD_NONE, CMD_NONE, };
static void ImportIMFEffect(ModCommand &m)
{
uint8 n;
switch (m.command)
{
case 0xE: if(m.param == 0)
;
else if(m.param == 0xF0)
m.param = 0xEF;
else if(m.param == 0x0F)
m.param = 0xFE;
else if(m.param & 0xF0)
m.param |= 0x0F;
else
m.param |= 0xF0;
break;
case 0xF: m.param = 0x20 | MIN(m.param >> 4, 0x0F);
break;
case 0x14: case 0x15: if(m.param >> 4)
m.param = 0xF0 | MIN(m.param >> 4, 0x0F);
else
m.param |= 0xE0;
break;
case 0x16: m.param = (0xFF - m.param) / 2u;
break;
case 0x1F: m.param = MIN(m.param << 1, 0xFF);
break;
case 0x21:
n = 0;
switch (m.param >> 4)
{
case 0:
break;
default: case 0x1: case 0xF: m.command = CMD_NONE;
break;
case 0x3: n = 0x20;
break;
case 0x5: n = 0x30;
break;
case 0x8: n = 0x40;
break;
case 0xA: n = 0xB0;
break;
case 0xB: n = 0xE0;
break;
case 0xC: case 0xD: if(!m.param)
m.command = CMD_NONE;
break;
case 0xE:
m.param = 0x77;
break;
case 0x18: if(!m.param)
m.command = CMD_NONE;
break;
}
if(n)
m.param = n | (m.param & 0x0F);
break;
}
m.command = (m.command < CountOf(imfEffects)) ? imfEffects[m.command] : CMD_NONE;
if(m.command == CMD_VOLUME && m.volcmd == VOLCMD_NONE)
{
m.volcmd = VOLCMD_VOLUME;
m.vol = m.param;
m.command = CMD_NONE;
m.param = 0;
}
}
static bool ValidateHeader(const IMFFileHeader &fileHeader)
{
if(std::memcmp(fileHeader.im10, "IM10", 4)
|| fileHeader.ordNum > 256
|| fileHeader.insNum >= MAX_INSTRUMENTS)
{
return false;
}
bool channelFound = false;
for(const auto &chn : fileHeader.channels)
{
switch(chn.status)
{
case 0: channelFound = true;
break;
case 1: channelFound = true;
break;
case 2: break;
default: return false;
}
}
if(!channelFound)
{
return false;
}
return true;
}
static uint64 GetHeaderMinimumAdditionalSize(const IMFFileHeader &fileHeader)
{
return 256 + fileHeader.patNum * 4 + fileHeader.insNum * sizeof(IMFInstrument);
}
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderIMF(MemoryFileReader file, const uint64 *pfilesize)
{
IMFFileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return ProbeWantMoreData;
}
if(!ValidateHeader(fileHeader))
{
return ProbeFailure;
}
return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
}
bool CSoundFile::ReadIMF(FileReader &file, ModLoadingFlags loadFlags)
{
IMFFileHeader fileHeader;
file.Rewind();
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;
}
std::bitset<32> ignoreChannels; uint8 detectedChannels = 0;
for(uint8 chn = 0; chn < 32; chn++)
{
ChnSettings[chn].Reset();
ChnSettings[chn].nPan = fileHeader.channels[chn].panning * 256 / 255;
mpt::String::Read<mpt::String::nullTerminated>(ChnSettings[chn].szName, fileHeader.channels[chn].name);
switch(fileHeader.channels[chn].status)
{
case 0: detectedChannels = chn + 1;
break;
case 1: ChnSettings[chn].dwFlags = CHN_MUTE;
detectedChannels = chn + 1;
break;
case 2: ChnSettings[chn].dwFlags = CHN_MUTE;
ignoreChannels[chn] = true;
break;
default: return false;
}
}
InitializeGlobals(MOD_TYPE_IMF);
m_nChannels = detectedChannels;
m_modFormat.formatName = U_("Imago Orpheus");
m_modFormat.type = U_("imf");
m_modFormat.charset = mpt::CharsetCP437;
if(fileHeader.channels[0].status == 0)
{
CHANNELINDEX chn;
for(chn = 1; chn < 16; chn++)
if(fileHeader.channels[chn].status != 1)
break;
if(chn == 16)
for(chn = 1; chn < 16; chn++)
ChnSettings[chn].dwFlags.reset(CHN_MUTE);
}
mpt::String::Read<mpt::String::nullTerminated>(m_songName, fileHeader.title);
m_SongFlags.set(SONG_LINEARSLIDES, fileHeader.flags & IMFFileHeader::linearSlides);
m_nDefaultSpeed = fileHeader.tempo;
m_nDefaultTempo.Set(fileHeader.bpm);
m_nDefaultGlobalVolume = Clamp<uint8, uint8>(fileHeader.master, 0, 64) * 4;
m_nSamplePreAmp = Clamp<uint8, uint8>(fileHeader.amp, 4, 127);
m_nInstruments = fileHeader.insNum;
m_nSamples = 0;
uint8 orders[256];
file.ReadArray(orders);
ReadOrderFromArray(Order(), orders, fileHeader.ordNum, uint16_max, 0xFF);
if(loadFlags & loadPatternData)
Patterns.ResizeArray(fileHeader.patNum);
for(PATTERNINDEX pat = 0; pat < fileHeader.patNum; pat++)
{
const uint16 length = file.ReadUint16LE(), numRows = file.ReadUint16LE();
FileReader patternChunk = file.ReadChunk(length - 4);
if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, numRows))
{
continue;
}
ModCommand dummy;
ROWINDEX row = 0;
while(row < numRows)
{
uint8 mask = patternChunk.ReadUint8();
if(mask == 0)
{
row++;
continue;
}
uint8 channel = mask & 0x1F;
ModCommand &m = (channel < GetNumChannels()) ? *Patterns[pat].GetpModCommand(row, channel) : dummy;
if(mask & 0x20)
{
m.note = patternChunk.ReadUint8();
m.instr = patternChunk.ReadUint8();
if(m.note == 160)
{
m.note = NOTE_KEYOFF;
} else if(m.note == 255)
{
m.note = NOTE_NONE;
} else
{
m.note = (m.note >> 4) * 12 + (m.note & 0x0F) + 12 + 1;
if(!m.IsNoteOrEmpty())
{
m.note = NOTE_NONE;
}
}
}
if((mask & 0xC0) == 0xC0)
{
uint8 e1c = patternChunk.ReadUint8(); uint8 e1d = patternChunk.ReadUint8(); uint8 e2c = patternChunk.ReadUint8(); uint8 e2d = patternChunk.ReadUint8();
if(e1c == 0x0C)
{
m.vol = MIN(e1d, 0x40);
m.volcmd = VOLCMD_VOLUME;
m.command = e2c;
m.param = e2d;
} else if(e2c == 0x0C)
{
m.vol = MIN(e2d, 0x40);
m.volcmd = VOLCMD_VOLUME;
m.command = e1c;
m.param = e1d;
} else if(e1c == 0x0A)
{
m.vol = e1d * 64 / 255;
m.volcmd = VOLCMD_PANNING;
m.command = e2c;
m.param = e2d;
} else if(e2c == 0x0A)
{
m.vol = e2d * 64 / 255;
m.volcmd = VOLCMD_PANNING;
m.command = e1c;
m.param = e1d;
} else
{
m.command = e2c;
m.param = e2d;
}
} else if(mask & 0xC0)
{
m.command = patternChunk.ReadUint8();
m.param = patternChunk.ReadUint8();
}
if(m.command)
ImportIMFEffect(m);
if(ignoreChannels[channel] && m.IsGlobalCommand())
m.command = CMD_NONE;
}
}
SAMPLEINDEX firstSample = 1;
for(INSTRUMENTINDEX ins = 0; ins < GetNumInstruments(); ins++)
{
ModInstrument *instr = AllocateInstrument(ins + 1);
IMFInstrument instrumentHeader;
if(!file.ReadStruct(instrumentHeader) || instr == nullptr)
{
continue;
}
instrumentHeader.ConvertToMPT(*instr, firstSample);
for(SAMPLEINDEX smp = 0; smp < instrumentHeader.smpNum; smp++)
{
IMFSample sampleHeader;
file.ReadStruct(sampleHeader);
const SAMPLEINDEX smpID = firstSample + smp;
if(memcmp(sampleHeader.is10, "IS10", 4) || smpID >= MAX_SAMPLES)
{
continue;
}
m_nSamples = smpID;
ModSample &sample = Samples[smpID];
sampleHeader.ConvertToMPT(sample);
mpt::String::Copy(m_szNames[smpID], sample.filename);
if(sampleHeader.length)
{
FileReader sampleChunk = file.ReadChunk(sampleHeader.length);
if(loadFlags & loadSampleData)
{
SampleIO(
sample.uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit,
SampleIO::mono,
SampleIO::littleEndian,
SampleIO::signedPCM)
.ReadSample(sample, sampleChunk);
}
}
}
firstSample += instrumentHeader.smpNum;
}
return true;
}
OPENMPT_NAMESPACE_END