#include "stdafx.h"
#include "Loaders.h"
OPENMPT_NAMESPACE_BEGIN
struct OktIffChunk
{
enum ChunkIdentifiers
{
idCMOD = MagicBE("CMOD"),
idSAMP = MagicBE("SAMP"),
idSPEE = MagicBE("SPEE"),
idSLEN = MagicBE("SLEN"),
idPLEN = MagicBE("PLEN"),
idPATT = MagicBE("PATT"),
idPBOD = MagicBE("PBOD"),
idSBOD = MagicBE("SBOD"),
};
uint32be signature; uint32be chunksize; };
MPT_BINARY_STRUCT(OktIffChunk, 8)
struct OktSample
{
char name[20];
uint32be length; uint16be loopStart; uint16be loopLength; uint16be volume; uint16be type; };
MPT_BINARY_STRUCT(OktSample, 32)
static void ReadOKTSamples(FileReader &chunk, std::vector<bool> &sample7bit, CSoundFile &sndFile)
{
sndFile.m_nSamples = std::min<SAMPLEINDEX>(static_cast<SAMPLEINDEX>(chunk.BytesLeft() / sizeof(OktSample)), MAX_SAMPLES - 1u);
sample7bit.resize(sndFile.GetNumSamples());
for(SAMPLEINDEX smp = 1; smp <= sndFile.GetNumSamples(); smp++)
{
ModSample &mptSmp = sndFile.GetSample(smp);
OktSample oktSmp;
chunk.ReadStruct(oktSmp);
mptSmp.Initialize();
mpt::String::Read<mpt::String::maybeNullTerminated>(sndFile.m_szNames[smp], oktSmp.name);
mptSmp.nC5Speed = 8287;
mptSmp.nGlobalVol = 64;
mptSmp.nVolume = std::min<uint16>(oktSmp.volume, 64u) * 4u;
mptSmp.nLength = oktSmp.length & ~1; const SmpLength loopStart = oktSmp.loopStart * 2;
const SmpLength loopLength = oktSmp.loopLength * 2;
if(loopLength > 2 && loopStart + loopLength <= mptSmp.nLength)
{
mptSmp.uFlags.set(CHN_SUSTAINLOOP);
mptSmp.nSustainStart = loopStart;
mptSmp.nSustainEnd = loopStart + loopLength;
}
sample7bit[smp - 1] = (oktSmp.type == 0 || oktSmp.type == 2);
}
}
static void ReadOKTPattern(FileReader &chunk, PATTERNINDEX pat, CSoundFile &sndFile)
{
if(!chunk.CanRead(2))
{
sndFile.Patterns.Insert(pat, 64);
return;
}
ROWINDEX rows = Clamp(static_cast<ROWINDEX>(chunk.ReadUint16BE()), ROWINDEX(1), MAX_PATTERN_ROWS);
if(!sndFile.Patterns.Insert(pat, rows))
{
return;
}
const CHANNELINDEX chns = sndFile.GetNumChannels();
for(ROWINDEX row = 0; row < rows; row++)
{
ModCommand *rowCmd = sndFile.Patterns[pat].GetRow(row);
for(CHANNELINDEX chn = 0; chn < chns; chn++)
{
ModCommand &m = rowCmd[chn];
uint8 note = chunk.ReadUint8();
uint8 instr = chunk.ReadUint8();
uint8 effect = chunk.ReadUint8();
m.param = chunk.ReadUint8();
if(note > 0 && note <= 36)
{
m.note = note + (NOTE_MIDDLEC - 13);
m.instr = instr + 1;
} else
{
m.instr = 0;
}
switch(effect)
{
case 0: m.param = 0;
break;
case 1: m.command = CMD_PORTAMENTOUP;
m.param &= 0x0F;
break;
case 2: m.command = CMD_PORTAMENTODOWN;
m.param &= 0x0F;
break;
#if 0#endif
case 12: if (m.param)
m.command = CMD_ARPEGGIO;
break;
case 13: if (m.param)
{
m.command = CMD_NOTESLIDEDOWN;
m.param = 0x10 | MIN(0x0F, m.param);
}
break;
case 30: if (m.param)
{
m.command = CMD_NOTESLIDEUP;
m.param = 0x10 | MIN(0x0F, m.param);
}
break;
case 21: if (m.param)
{
m.command = CMD_NOTESLIDEDOWN;
m.param = 0x50 | MIN(0x0F, m.param);
}
break;
case 17: if (m.param)
{
m.command = CMD_NOTESLIDEUP;
m.param = 0x50 | MIN(0x0F, m.param);
}
break;
case 15: m.command = CMD_MODCMDEX;
m.param = !!m.param;
break;
case 25: m.command = CMD_POSITIONJUMP;
break;
case 27: m.Clear();
m.note = NOTE_KEYOFF;
break;
case 28: m.command = CMD_SPEED; break;
case 31: m.command = CMD_VOLUMESLIDE;
switch (m.param >> 4)
{
case 4:
if (m.param != 0x40)
{
m.param &= 0x0F; break;
}
MPT_FALLTHROUGH;
case 0: case 1: case 2: case 3:
m.volcmd = VOLCMD_VOLUME;
m.vol = m.param;
m.command = CMD_NONE;
m.param = 0;
break;
case 5:
m.param = (m.param & 0x0F) << 4; break;
case 6:
m.param = 0xF0 | MIN(m.param & 0x0F, 0x0E); break;
case 7:
m.param = (MIN(m.param & 0x0F, 0x0E) << 4) | 0x0F; break;
default:
m.command = CMD_NONE;
m.param = 0;
break;
}
break;
#if 0#endif
default:
m.command = m.param = 0;
break;
}
}
}
}
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderOKT(MemoryFileReader file, const uint64 *pfilesize)
{
if(!file.CanRead(8))
{
return ProbeWantMoreData;
}
if(!file.ReadMagic("OKTASONG"))
{
return ProbeFailure;
}
OktIffChunk iffHead;
if(!file.ReadStruct(iffHead))
{
return ProbeWantMoreData;
}
if(iffHead.chunksize == 0)
{
return ProbeFailure;
}
if((iffHead.signature & 0x80808080u) != 0) {
return ProbeFailure;
}
MPT_UNREFERENCED_PARAMETER(pfilesize);
return ProbeSuccess;
}
bool CSoundFile::ReadOKT(FileReader &file, ModLoadingFlags loadFlags)
{
file.Rewind();
if(!file.ReadMagic("OKTASONG"))
{
return false;
}
std::vector<FileReader> patternChunks;
std::vector<FileReader> sampleChunks;
std::vector<bool> sample7bit; ORDERINDEX numOrders = 0;
InitializeGlobals(MOD_TYPE_OKT);
m_modFormat.formatName = U_("Oktalyzer");
m_modFormat.type = U_("okt");
m_modFormat.charset = mpt::CharsetISO8859_1;
while(file.CanRead(sizeof(OktIffChunk)))
{
OktIffChunk iffHead;
if(!file.ReadStruct(iffHead))
{
break;
}
FileReader chunk = file.ReadChunk(iffHead.chunksize);
if(!chunk.IsValid())
{
break;
}
switch(iffHead.signature)
{
case OktIffChunk::idCMOD:
if(m_nChannels > 0 || chunk.GetLength() < 8)
{
break;
}
for(CHANNELINDEX chn = 0; chn < 4; chn++)
{
const uint8 ch1 = chunk.ReadUint8(), ch2 = chunk.ReadUint8();
if(ch1 || ch2)
{
ChnSettings[m_nChannels].Reset();
ChnSettings[m_nChannels++].nPan = (((chn & 3) == 1) || ((chn & 3) == 2)) ? 0xC0 : 0x40;
}
ChnSettings[m_nChannels].Reset();
ChnSettings[m_nChannels++].nPan = (((chn & 3) == 1) || ((chn & 3) == 2)) ? 0xC0 : 0x40;
}
if(loadFlags == onlyVerifyHeader)
{
return true;
}
break;
case OktIffChunk::idSAMP:
if(m_nSamples > 0)
{
break;
}
ReadOKTSamples(chunk, sample7bit, *this);
break;
case OktIffChunk::idSPEE:
if(chunk.GetLength() >= 2)
{
m_nDefaultSpeed = Clamp(chunk.ReadUint16BE(), uint16(1), uint16(255));
}
break;
case OktIffChunk::idSLEN:
break;
case OktIffChunk::idPLEN:
if(chunk.GetLength() >= 2)
{
numOrders = chunk.ReadUint16BE();
}
break;
case OktIffChunk::idPATT:
ReadOrderFromFile<uint8>(Order(), chunk, chunk.GetLength(), 0xFF, 0xFE);
break;
case OktIffChunk::idPBOD:
if(patternChunks.size() < 256)
{
patternChunks.push_back(chunk);
}
break;
case OktIffChunk::idSBOD:
if(sampleChunks.size() < MAX_SAMPLES - 1 && chunk.GetLength() > 0)
{
sampleChunks.push_back(chunk);
}
break;
}
}
if(m_nChannels == 0)
return false;
m_nDefaultTempo.Set(125);
m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME;
m_nSamplePreAmp = m_nVSTiVolume = 48;
m_nMinPeriod = 0x71 * 4;
m_nMaxPeriod = 0x358 * 4;
Order().resize(numOrders);
if(loadFlags & loadPatternData)
{
Patterns.ResizeArray(static_cast<PATTERNINDEX>(patternChunks.size()));
for(PATTERNINDEX pat = 0; pat < patternChunks.size(); pat++)
{
ReadOKTPattern(patternChunks[pat], pat, *this);
}
}
size_t fileSmp = 0;
for(SAMPLEINDEX smp = 1; smp < m_nSamples; smp++)
{
if(fileSmp >= sampleChunks.size() || !(loadFlags & loadSampleData))
break;
ModSample &mptSample = Samples[smp];
if(mptSample.nLength == 0)
continue;
LimitMax(mptSample.nLength, mpt::saturate_cast<SmpLength>(sampleChunks[fileSmp].GetLength()));
SampleIO(
SampleIO::_8bit,
SampleIO::mono,
SampleIO::bigEndian,
sample7bit[smp - 1] ? SampleIO::PCM7to8 : SampleIO::signedPCM)
.ReadSample(mptSample, sampleChunks[fileSmp]);
fileSmp++;
}
return true;
}
OPENMPT_NAMESPACE_END