| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 | /* * 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_BEGINstruct 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
 |