Initial community commit
This commit is contained in:
parent
537bcbc862
commit
fc06254474
16440 changed files with 4239995 additions and 2 deletions
268
Src/external_dependencies/openmpt-trunk/soundlib/Load_c67.cpp
Normal file
268
Src/external_dependencies/openmpt-trunk/soundlib/Load_c67.cpp
Normal file
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
* 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
|
Loading…
Add table
Add a link
Reference in a new issue