winamp/Src/external_dependencies/openmpt-trunk/soundlib/Load_c67.cpp
2024-09-24 14:54:57 +02:00

269 lines
7.0 KiB
C++

/*
* Load_c67.cpp
* ------------
* Purpose: C67 (CDFM Composer) module loader
* Notes : C67 is the composer format; 670 files can be converted back to C67 using the converter that comes with CDFM.
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "Loaders.h"
OPENMPT_NAMESPACE_BEGIN
struct C67SampleHeader
{
uint32le unknown; // Probably placeholder for in-memory address, 0 on disk
uint32le length;
uint32le loopStart;
uint32le loopEnd;
};
MPT_BINARY_STRUCT(C67SampleHeader, 16)
struct C67FileHeader
{
uint8 speed;
uint8 restartPos;
char sampleNames[32][13];
C67SampleHeader samples[32];
char fmInstrNames[32][13];
uint8 fmInstr[32][11];
uint8 orders[256];
};
MPT_BINARY_STRUCT(C67FileHeader, 1954)
static bool ValidateHeader(const C67FileHeader &fileHeader)
{
if(fileHeader.speed < 1 || fileHeader.speed > 15)
return false;
for(auto ord : fileHeader.orders)
{
if(ord >= 128 && ord != 0xFF)
return false;
}
bool anyNonSilent = false;
for(SAMPLEINDEX smp = 0; smp < 32; smp++)
{
if(fileHeader.sampleNames[smp][12] != 0
|| fileHeader.samples[smp].unknown != 0
|| fileHeader.samples[smp].length > 0xFFFFF
|| fileHeader.fmInstrNames[smp][12] != 0
|| (fileHeader.fmInstr[smp][0] & 0xF0) // No OPL3
|| (fileHeader.fmInstr[smp][5] & 0xFC) // No OPL3
|| (fileHeader.fmInstr[smp][10] & 0xFC)) // No OPL3
{
return false;
}
if(fileHeader.samples[smp].length != 0 && fileHeader.samples[smp].loopEnd < 0xFFFFF)
{
if(fileHeader.samples[smp].loopEnd > fileHeader.samples[smp].length
|| fileHeader.samples[smp].loopStart > fileHeader.samples[smp].loopEnd)
{
return false;
}
}
if(!anyNonSilent && (fileHeader.samples[smp].length != 0 || memcmp(fileHeader.fmInstr[smp], "\0\0\0\0\0\0\0\0\0\0\0", 11)))
{
anyNonSilent = true;
}
}
return anyNonSilent;
}
static uint64 GetHeaderMinimumAdditionalSize(const C67FileHeader &)
{
return 1024; // Pattern offsets and lengths
}
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderC67(MemoryFileReader file, const uint64 *pfilesize)
{
C67FileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return ProbeWantMoreData;
}
if(!ValidateHeader(fileHeader))
{
return ProbeFailure;
}
return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
}
static void TranslateVolume(ModCommand &m, uint8 volume, bool isFM)
{
// CDFM uses a linear volume scale for FM instruments.
// ScreamTracker, on the other hand, directly uses the OPL chip's logarithmic volume scale.
// Neither FM nor PCM instruments can be fully muted in CDFM.
static constexpr uint8 fmVolume[16] =
{
0x08, 0x10, 0x18, 0x20, 0x28, 0x2C, 0x30, 0x34,
0x36, 0x38, 0x3A, 0x3C, 0x3D, 0x3E, 0x3F, 0x40,
};
volume &= 0x0F;
m.volcmd = VOLCMD_VOLUME;
m.vol = isFM ? fmVolume[volume] : (4u + volume * 4u);
}
bool CSoundFile::ReadC67(FileReader &file, ModLoadingFlags loadFlags)
{
C67FileHeader 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;
}
// Validate pattern offsets and lengths
uint32le patOffsets[128], patLengths[128];
file.ReadArray(patOffsets);
file.ReadArray(patLengths);
for(PATTERNINDEX pat = 0; pat < 128; pat++)
{
if(patOffsets[pat] > 0xFFFFFF
|| patLengths[pat] < 3 // Smallest well-formed pattern consists of command 0x40 followed by command 0x60
|| patLengths[pat] > 0x1000 // Any well-formed pattern is smaller than this
|| !file.LengthIsAtLeast(2978 + patOffsets[pat] + patLengths[pat]))
{
return false;
}
}
InitializeGlobals(MOD_TYPE_S3M);
InitializeChannels();
m_modFormat.formatName = U_("CDFM");
m_modFormat.type = U_("c67");
m_modFormat.madeWithTracker = U_("Composer 670");
m_modFormat.charset = mpt::Charset::CP437;
m_nDefaultSpeed = fileHeader.speed;
m_nDefaultTempo.Set(143);
Order().SetRestartPos(fileHeader.restartPos);
m_nSamples = 64;
m_nChannels = 4 + 9;
m_playBehaviour.set(kOPLBeatingOscillators);
m_SongFlags.set(SONG_IMPORTED);
// Pan PCM channels only
for(CHANNELINDEX chn = 0; chn < 4; chn++)
{
ChnSettings[chn].nPan = (chn & 1) ? 192 : 64;
}
// PCM instruments
for(SAMPLEINDEX smp = 0; smp < 32; smp++)
{
ModSample &mptSmp = Samples[smp + 1];
mptSmp.Initialize(MOD_TYPE_S3M);
m_szNames[smp + 1] = mpt::String::ReadBuf(mpt::String::nullTerminated, fileHeader.sampleNames[smp]);
mptSmp.nLength = fileHeader.samples[smp].length;
if(fileHeader.samples[smp].loopEnd <= fileHeader.samples[smp].length)
{
mptSmp.nLoopStart = fileHeader.samples[smp].loopStart;
mptSmp.nLoopEnd = fileHeader.samples[smp].loopEnd;
mptSmp.uFlags = CHN_LOOP;
}
mptSmp.nC5Speed = 8287;
}
// OPL instruments
for(SAMPLEINDEX smp = 0; smp < 32; smp++)
{
ModSample &mptSmp = Samples[smp + 33];
mptSmp.Initialize(MOD_TYPE_S3M);
m_szNames[smp + 33] = mpt::String::ReadBuf(mpt::String::nullTerminated, fileHeader.fmInstrNames[smp]);
// Reorder OPL patch bytes (interleave modulator and carrier)
const auto &fm = fileHeader.fmInstr[smp];
OPLPatch patch{{}};
patch[0] = fm[1]; patch[1] = fm[6];
patch[2] = fm[2]; patch[3] = fm[7];
patch[4] = fm[3]; patch[5] = fm[8];
patch[6] = fm[4]; patch[7] = fm[9];
patch[8] = fm[5]; patch[9] = fm[10];
patch[10] = fm[0];
mptSmp.SetAdlib(true, patch);
}
ReadOrderFromArray<uint8>(Order(), fileHeader.orders, 256, 0xFF);
Patterns.ResizeArray(128);
for(PATTERNINDEX pat = 0; pat < 128; pat++)
{
file.Seek(2978 + patOffsets[pat]);
FileReader patChunk = file.ReadChunk(patLengths[pat]);
if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64))
{
continue;
}
CPattern &pattern = Patterns[pat];
ROWINDEX row = 0;
while(row < 64 && patChunk.CanRead(1))
{
uint8 cmd = patChunk.ReadUint8();
if(cmd <= 0x0C)
{
// Note, instrument, volume
ModCommand &m = *pattern.GetpModCommand(row, cmd);
const auto [note, instrVol] = patChunk.ReadArray<uint8, 2>();
bool fmChn = (cmd >= 4);
m.note = NOTE_MIN + (fmChn ? 12 : 36) + (note & 0x0F) + ((note >> 4) & 0x07) * 12;
m.instr = (fmChn ? 33 : 1) + (instrVol >> 4) + ((note & 0x80) >> 3);
TranslateVolume(m, instrVol, fmChn);
} else if(cmd >= 0x20 && cmd <= 0x2C)
{
// Volume
TranslateVolume(*pattern.GetpModCommand(row, cmd - 0x20), patChunk.ReadUint8(), cmd >= 0x24);
} else if(cmd == 0x40)
{
// Delay (row done)
row += patChunk.ReadUint8();
} else if(cmd == 0x60)
{
// End of pattern
if(row > 0)
{
pattern.GetpModCommand(row - 1, 0)->command = CMD_PATTERNBREAK;
}
break;
} else
{
return false;
}
}
}
if(loadFlags & loadSampleData)
{
for(SAMPLEINDEX smp = 1; smp <= 32; smp++)
{
SampleIO(SampleIO::_8bit, SampleIO::mono, SampleIO::littleEndian, SampleIO::unsignedPCM).ReadSample(Samples[smp], file);
}
}
return true;
}
OPENMPT_NAMESPACE_END