#include "stdafx.h"
#include "Loaders.h"
OPENMPT_NAMESPACE_BEGIN
struct _669FileHeader
{
char magic[2]; char songMessage[108]; uint8 samples; uint8 patterns; uint8 restartPos;
uint8 orders[128];
uint8 tempoList[128];
uint8 breaks[128];
};
MPT_BINARY_STRUCT(_669FileHeader, 497)
struct _669Sample
{
char filename[13];
uint32le length;
uint32le loopStart;
uint32le loopEnd;
void ConvertToMPT(ModSample &mptSmp) const
{
mptSmp.Initialize();
mptSmp.nC5Speed = 8363;
mptSmp.nLength = length;
mptSmp.nLoopStart = loopStart;
mptSmp.nLoopEnd = loopEnd;
if(mptSmp.nLoopEnd > mptSmp.nLength && mptSmp.nLoopStart == 0)
{
mptSmp.nLoopEnd = 0;
}
if(mptSmp.nLoopEnd != 0)
{
mptSmp.uFlags = CHN_LOOP;
mptSmp.SanitizeLoops();
}
}
};
MPT_BINARY_STRUCT(_669Sample, 25)
static bool ValidateHeader(const _669FileHeader &fileHeader)
{
if((std::memcmp(fileHeader.magic, "if", 2) && std::memcmp(fileHeader.magic, "JN", 2))
|| fileHeader.samples > 64
|| fileHeader.restartPos >= 128
|| fileHeader.patterns > 128)
{
return false;
}
for(std::size_t i = 0; i < CountOf(fileHeader.breaks); i++)
{
if(fileHeader.orders[i] >= 128 && fileHeader.orders[i] < 0xFE)
{
return false;
}
if(fileHeader.orders[i] < 128 && fileHeader.tempoList[i] == 0)
{
return false;
}
if(fileHeader.breaks[i] >= 64)
{
return false;
}
}
return true;
}
static uint64 GetHeaderMinimumAdditionalSize(const _669FileHeader &fileHeader)
{
return fileHeader.samples * sizeof(_669Sample) + fileHeader.patterns * 1536u;
}
CSoundFile::ProbeResult CSoundFile::ProbeFileHeader669(MemoryFileReader file, const uint64 *pfilesize)
{
_669FileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return ProbeWantMoreData;
}
if(!ValidateHeader(fileHeader))
{
return ProbeFailure;
}
return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
}
bool CSoundFile::Read669(FileReader &file, ModLoadingFlags loadFlags)
{
_669FileHeader fileHeader;
file.Rewind();
if(!file.ReadStruct(fileHeader))
{
return false;
}
if(!ValidateHeader(fileHeader))
{
return false;
}
if(loadFlags == onlyVerifyHeader)
{
return true;
}
if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
{
return false;
}
InitializeGlobals(MOD_TYPE_669);
m_nMinPeriod = 28 << 2;
m_nMaxPeriod = 1712 << 3;
m_nDefaultTempo.Set(78);
m_nDefaultSpeed = 4;
m_nChannels = 8;
#ifdef MODPLUG_TRACEKR
m_SongFlags.set(SONG_LINEARSLIDES);
#endif
m_modFormat.formatName = U_("Composer 669");
m_modFormat.type = U_("669");
m_modFormat.madeWithTracker = !memcmp(fileHeader.magic, "if", 2) ? UL_("Composer 669") : UL_("UNIS 669");
m_modFormat.charset = mpt::CharsetCP437;
m_nSamples = fileHeader.samples;
for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++)
{
_669Sample sampleHeader;
file.ReadStruct(sampleHeader);
if(sampleHeader.length >= 0x4000000)
return false;
sampleHeader.ConvertToMPT(Samples[smp]);
mpt::String::Read<mpt::String::maybeNullTerminated>(m_szNames[smp], sampleHeader.filename);
}
mpt::String::Read<mpt::String::spacePadded>(m_songName, fileHeader.songMessage, 36);
m_songMessage.ReadFixedLineLength(mpt::byte_cast<const mpt::byte*>(fileHeader.songMessage), 108, 36, 0);
ReadOrderFromArray(Order(), fileHeader.orders, MPT_ARRAY_COUNT(fileHeader.orders), 0xFF, 0xFE);
if(Order()[fileHeader.restartPos] < fileHeader.patterns)
Order().SetRestartPos(fileHeader.restartPos);
for(CHANNELINDEX chn = 0; chn < 8; chn++)
{
ChnSettings[chn].Reset();
ChnSettings[chn].nPan = (chn & 1) ? 0xD0 : 0x30;
}
Patterns.ResizeArray(fileHeader.patterns);
for(PATTERNINDEX pat = 0; pat < fileHeader.patterns; pat++)
{
if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64))
{
file.Skip(64 * 8 * 3);
continue;
}
const ModCommand::COMMAND effTrans[] =
{
CMD_PORTAMENTOUP, CMD_PORTAMENTODOWN, CMD_TONEPORTAMENTO, CMD_S3MCMDEX, CMD_VIBRATO, CMD_SPEED, CMD_PANNINGSLIDE, CMD_RETRIG, };
uint8 effect[8] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
for(ROWINDEX row = 0; row < 64; row++)
{
PatternRow m = Patterns[pat].GetRow(row);
for(CHANNELINDEX chn = 0; chn < 8; chn++, m++)
{
uint8 data[3];
file.ReadArray(data);
uint8 note = data[0] >> 2;
uint8 instr = ((data[0] & 0x03) << 4) | (data[1] >> 4);
uint8 vol = data[1] & 0x0F;
if(data[0] < 0xFE)
{
m->note = note + 36 + NOTE_MIN;
m->instr = instr + 1;
effect[chn] = 0xFF;
}
if(data[0] <= 0xFE)
{
m->volcmd = VOLCMD_VOLUME;
m->vol = ((vol * 64 + 8) / 15);
}
if(data[2] != 0xFF)
{
effect[chn] = data[2];
}
if((data[2] & 0x0F) == 0 && data[2] != 0x30)
{
effect[chn] = 0xFF;
}
if(effect[chn] == 0xFF)
{
continue;
}
m->param = effect[chn] & 0x0F;
uint8 command = effect[chn] >> 4;
if(command < static_cast<uint8>(CountOf(effTrans)))
{
m->command = effTrans[command];
} else
{
m->command = CMD_NONE;
continue;
}
switch(command)
{
case 3:
#ifdef MODPLUG_TRACKER
m->command = CMD_PORTAMENTOUP;
m->param |= 0xF0;
#else
m->param |= 0x20;
#endif
effect[chn] = 0xFF;
break;
case 4:
#ifdef MODPLUG_TRACKER
m->command = CMD_ARPEGGIO;
#endif
m->param |= (m->param << 4);
break;
case 5:
effect[chn] = 0xFF;
break;
case 6:
switch(m->param)
{
case 0:
m->param = 0x4F;
break;
case 1:
m->param = 0xF4;
break;
default:
m->command = CMD_NONE;
}
break;
}
}
}
if(fileHeader.breaks[pat] < 63)
{
Patterns[pat].WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(fileHeader.breaks[pat]).RetryNextRow());
}
Patterns[pat].WriteEffect(EffectWriter(CMD_SPEED, fileHeader.tempoList[pat]).RetryNextRow());
}
if(loadFlags & loadSampleData)
{
const SampleIO sampleIO(
SampleIO::_8bit,
SampleIO::mono,
SampleIO::littleEndian,
SampleIO::unsignedPCM);
for(SAMPLEINDEX n = 1; n <= m_nSamples; n++)
{
sampleIO.ReadSample(Samples[n], file);
}
}
return true;
}
OPENMPT_NAMESPACE_END