| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 | /* * Load_fmt.cpp * ------------ * Purpose: Davey W Taylor's FM Tracker module loader * Notes  : (currently none) * 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 FMTChannelSetting{	char name[8];	char settings[11];};MPT_BINARY_STRUCT(FMTChannelSetting, 19)struct FMTFileHeader{	char magic[11];  // Includes format version number for simplicity	char trackerName[20];	char songName[32];	FMTChannelSetting channels[8];	uint8 lastRow;	uint8 lastOrder;	uint8 lastPattern;};MPT_BINARY_STRUCT(FMTFileHeader, 218)static uint64 GetHeaderMinimumAdditionalSize(const FMTFileHeader &fileHeader){	// Order list + pattern delays, pattern mapping + at least one byte per channel	return (fileHeader.lastOrder + 1u) * 2u + (fileHeader.lastPattern + 1u) * 9u;}static bool ValidateHeader(const FMTFileHeader &fileHeader){	if(memcmp(fileHeader.magic, "FMTracker\x01\x01", 11))		return false;	for(const auto &channel : fileHeader.channels)	{		// Reject anything that resembles OPL3		if((channel.settings[8] & 0xFC) || (channel.settings[9] & 0xFC) || (channel.settings[10] & 0xF0))			return false;	}	return true;}CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderFMT(MemoryFileReader file, const uint64 *pfilesize){	FMTFileHeader fileHeader;	if(!file.Read(fileHeader))		return ProbeWantMoreData;	if(!ValidateHeader(fileHeader))		return ProbeFailure;	return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));}bool CSoundFile::ReadFMT(FileReader &file, ModLoadingFlags loadFlags){	file.Rewind();	FMTFileHeader fileHeader;	if(!file.Read(fileHeader))		return false;	if(!ValidateHeader(fileHeader))		return false;	if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))		return false;	if(loadFlags == onlyVerifyHeader)		return true;	InitializeGlobals(MOD_TYPE_S3M);	InitializeChannels();	m_nChannels = 8;	m_nSamples = 8;	m_nDefaultTempo = TEMPO(45.5);  // 18.2 Hz timer	m_playBehaviour.set(kOPLNoteStopWith0Hz);	m_SongFlags.set(SONG_IMPORTED);	m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songName);	for(CHANNELINDEX chn = 0; chn < 8; chn++)	{		const auto name = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.channels[chn].name);		ChnSettings[chn].szName = name;		ModSample &mptSmp = Samples[chn + 1];		mptSmp.Initialize(MOD_TYPE_S3M);		OPLPatch patch{{}};		memcpy(patch.data(), fileHeader.channels[chn].settings, 11);		mptSmp.SetAdlib(true, patch);		mptSmp.nC5Speed = 8215;		m_szNames[chn + 1] = name;	}	const ORDERINDEX numOrders = fileHeader.lastOrder + 1u;	ReadOrderFromFile<uint8>(Order(), file, numOrders);	std::vector<uint8> delays;	file.ReadVector(delays, numOrders);	for(uint8 delay : delays)	{		if(delay < 1 || delay > 8)			return false;	}	m_nDefaultSpeed = delays[0];	const PATTERNINDEX numPatterns = fileHeader.lastPattern + 1u;	const ROWINDEX numRows = fileHeader.lastRow + 1u;	std::vector<uint8> patternMap;	file.ReadVector(patternMap, numPatterns);	Patterns.ResizeArray(numPatterns);	for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)	{		if(!(loadFlags & loadPatternData) || !Patterns.Insert(patternMap[pat], numRows))			break;		auto &pattern = Patterns[patternMap[pat]];		for(CHANNELINDEX chn = 0; chn < 8; chn++)		{			for(ROWINDEX row = 0; row < pattern.GetNumRows(); row++)			{				uint8 data = file.ReadUint8();				if(data & 0x80)				{					row += data & 0x7F;				} else				{					ModCommand &m = *pattern.GetpModCommand(row, chn);					if(data == 1)					{						m.note = NOTE_NOTECUT;					} else if(data >= 2 && data <= 97)					{						m.note = data + NOTE_MIN + 11u;						m.instr = static_cast<ModCommand::INSTR>(chn + 1u);					}				}			}		}	}	// Write song speed to patterns... due to a quirk in the original playback routine	// (delays is applied before notes are triggered, not afterwards), a pattern's delay	// already applies to the last row of the previous pattern.	// In case you wonder if anyone would ever notice: My own songs written with this tracker	// actively work around this issue and will sound wrong if tempo is changed on the first row.	for(ORDERINDEX ord = 0; ord < numOrders; ord++)	{		if(!Order().IsValidPat(ord))		{			if(PATTERNINDEX pat = Patterns.InsertAny(numRows); pat != PATTERNINDEX_INVALID)				Order()[ord] = pat;			else				continue;		}		auto m = Patterns[Order()[ord]].end() - 1;		auto delay = delays[(ord + 1u) % numOrders];		if(m->param == delay)			continue;		if(m->command == CMD_SPEED)		{			PATTERNINDEX newPat = Order().EnsureUnique(ord);			if(newPat != PATTERNINDEX_INVALID)				m = Patterns[newPat].end() - 1;		}		m->command = CMD_SPEED;		m->param = delay;	}	m_modFormat.formatName = U_("FM Tracker");	m_modFormat.type = U_("fmt");	m_modFormat.madeWithTracker = mpt::ToUnicode(mpt::Charset::CP437, mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.trackerName));	m_modFormat.charset = mpt::Charset::CP437;	return true;}OPENMPT_NAMESPACE_END
 |