shithub: ft2-clone

ref: f95626311367970a4e12789247baa2c245e6af9a
dir: /src/modloaders/ft2_load_mod.c/

View raw version
// NoiseTracker/ProTracker (or compatible) MOD loader

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include "../ft2_header.h"
#include "../ft2_module_loader.h"
#include "../ft2_sample_ed.h"
#include "../ft2_tables.h"
#include "../ft2_sysreqs.h"

enum // supported 31-sample .MOD types
{
	FORMAT_MK,   // ProTracker or compatible
	FORMAT_FLT4, // StarTrekker (4ch modules)
	FORMAT_FLT8, // StarTrekker (8ch modules)
	FORMAT_FT2,  // FT2 or compatible (multi-channel)
	FORMAT_NT,   // NoiseTracker
	FORMAT_HMNT, // His Master's NoiseTracker (special one)

	FORMAT_UNKNOWN
};

static uint8_t getModType(uint8_t *numChannels, const char *id);

bool loadMOD(FILE *f, uint32_t filesize)
{
	uint8_t bytes[4], modFormat, numChannels;
	int16_t i, j, k;
	uint16_t a, b, period;
	tonTyp *ton;
	sampleTyp *s;
	songMOD31HeaderTyp h_MOD31;

	tmpLinearPeriodsFlag = false; // use Amiga periods

	if (filesize < sizeof (h_MOD31))
	{
		loaderMsgBox("Error: This file is either not a module, or is not supported.");
		return false;
	}

	memset(&h_MOD31, 0, sizeof (songMOD31HeaderTyp));
	if (fread(&h_MOD31, 1, sizeof (h_MOD31), f) != sizeof (h_MOD31))
	{
		loaderMsgBox("Error: This file is either not a module, or is not supported.");
		return false;
	}

	modFormat = getModType(&numChannels, h_MOD31.sig);
	if (modFormat == FORMAT_UNKNOWN)
	{
		loaderMsgBox("Error: This file is either not a module, or is not supported.");
		return false;
	}

	songTmp.antChn = numChannels;
	songTmp.len = h_MOD31.len;
	songTmp.repS = h_MOD31.repS;
	songTmp.initialTempo = songTmp.tempo = 6;
	songTmp.speed = 125;

	memcpy(songTmp.songTab, h_MOD31.songTab, 128);

	if (modFormat == FORMAT_MK && songTmp.len == 129)
		songTmp.len = 127; // fixes a specific copy of beatwave.mod (FIXME: Do we want to keep this hack?)

	if (songTmp.repS >= songTmp.len)
		songTmp.repS = 0;

	if (songTmp.antChn == 0 || songTmp.len < 1 || songTmp.len > 128)
	{
		loaderMsgBox("Error: This file is either not a module, or is not supported.");
		return false;
	}

	for (a = 0; a < 31; a++)
	{
		songMODInstrHeaderTyp *smp = &h_MOD31.instr[a];

		// copy over sample name if format isn't "His Master's Noisetracker" (junk sample names)
		if (modFormat != FORMAT_HMNT)
			memcpy(songTmp.instrName[1+a], smp->name, 22);
	}

	memcpy(songTmp.name, h_MOD31.name, 20);

	// count number of patterns
	b = 0;
	for (a = 0; a < 128; a++)
	{
		if (modFormat == FORMAT_FLT8)
			songTmp.songTab[a] >>= 1;

		if (songTmp.songTab[a] > b)
			b = songTmp.songTab[a];
	}
	b++;

	// load pattern data
	if (modFormat != FORMAT_FLT8)
	{
		// normal pattern loading

		for (a = 0; a < b; a++)
		{
			if (!allocateTmpPatt(a, 64))
			{
				loaderMsgBox("Not enough memory!");
				return false;
			}

			for (j = 0; j < 64; j++)
			{
				for (k = 0; k < songTmp.antChn; k++)
				{
					ton = &pattTmp[a][(j * MAX_VOICES) + k];
					fread(bytes, 1, 4, f);

					// period to note
					period = ((bytes[0] & 0x0F) << 8) | bytes[1];
					for (i = 0; i < 8*12; i++)
					{
						if (period >= amigaPeriod[i])
						{
							ton->ton = (uint8_t)i + 1;
							break;
						}
					}

					ton->instr = (bytes[0] & 0xF0) | (bytes[2] >> 4);
					ton->effTyp = bytes[2] & 0x0F;
					ton->eff = bytes[3];
				}
			}

			if (tmpPatternEmpty(a))
			{
				if (pattTmp[a] != NULL)
				{
					free(pattTmp[a]);
					pattTmp[a] = NULL;
				}
			}
		}
	}
	else
	{
		// FLT8 pattern loading

		for (a = 0; a < b; a++)
		{
			if (!allocateTmpPatt(a, 64))
			{
				loaderMsgBox("Not enough memory!");
				return false;
			}
		}

		for (a = 0; a < b*2; a++)
		{
			int32_t pattern = a >> 1;
			int32_t chnOffset = (a & 1) * 4;

			for (j = 0; j < 64; j++)
			{
				for (k = 0; k < 4; k++)
				{
					ton = &pattTmp[pattern][(j * MAX_VOICES) + (k+chnOffset)];
					fread(bytes, 1, 4, f);

					// period to note
					period = ((bytes[0] & 0x0F) << 8) | bytes[1];
					for (i = 0; i < 8*12; i++)
					{
						if (period >= amigaPeriod[i])
						{
							ton->ton = (uint8_t)i + 1;
							break;
						}
					}

					ton->instr = (bytes[0] & 0xF0) | (bytes[2] >> 4);
					ton->effTyp = bytes[2] & 0x0F;
					ton->eff = bytes[3];
				}
			}
		}

		for (a = 0; a < b; a++)
		{
			if (tmpPatternEmpty(a))
			{
				if (pattTmp[a] != NULL)
				{
					free(pattTmp[a]);
					pattTmp[a] = NULL;
				}
			}
		}
	}

	// pattern command conversion
	for (a = 0; a < b; a++)
	{
		if (pattTmp[a] == NULL)
			continue;

		for (j = 0; j < 64; j++)
		{
			for (k = 0; k < songTmp.antChn; k++)
			{
				ton = &pattTmp[a][(j * MAX_VOICES) + k];

				if (ton->effTyp == 0xC)
				{
					if (ton->eff > 64)
						ton->eff = 64;
				}
				else if (ton->effTyp == 0x1)
				{
					if (ton->eff == 0)
						ton->effTyp = 0;
				}
				else if (ton->effTyp == 0x2)
				{
					if (ton->eff == 0)
						ton->effTyp = 0;
				}
				else if (ton->effTyp == 0x5)
				{
					if (ton->eff == 0)
						ton->effTyp = 0x3;
				}
				else if (ton->effTyp == 0x6)
				{
					if (ton->eff == 0)
						ton->effTyp = 0x4;
				}
				else if (ton->effTyp == 0xA)
				{
					if (ton->eff == 0)
						ton->effTyp = 0;
				}
				else if (ton->effTyp == 0xE)
				{
					// check if certain E commands are empty
					if (ton->eff == 0x10 || ton->eff == 0x20 || ton->eff == 0xA0 || ton->eff == 0xB0)
					{
						ton->effTyp = 0;
						ton->eff = 0;
					}
				}

				if (modFormat == FORMAT_NT || modFormat == FORMAT_HMNT)
				{
					// any Dxx == D00 in NT/HMNT
					if (ton->effTyp == 0xD)
						ton->eff = 0;

					// effect F with param 0x00 does nothing in NT/HMNT
					if (ton->effTyp == 0xF && ton->eff == 0)
						ton->effTyp = 0;
				}
				else if (modFormat == FORMAT_FLT4 || modFormat == FORMAT_FLT8) // Startrekker
				{
					if (ton->effTyp == 0xE) // remove unsupported "assembly macros" command
					{
						ton->effTyp = 0;
						ton->eff = 0;
					}

					// Startrekker is always in vblank mode, and limits speed to 0x1F
					if (ton->effTyp == 0xF && ton->eff > 0x1F)
						ton->eff = 0x1F;
				}
			}
		}
	}

	for (a = 0; a < 31; a++)
	{
		if (h_MOD31.instr[a].len == 0)
			continue;

		if (!allocateTmpInstr(1+a))
		{
			loaderMsgBox("Not enough memory!");
			return false;
		}

		setNoEnvelope(instrTmp[1+a]);

		s = &instrTmp[1+a]->samp[0];

		// copy over sample name if format isn't "His Master's Noisetracker" (junk sample names)
		if (modFormat != FORMAT_HMNT)
			memcpy(s->name, songTmp.instrName[1+a], 22);

		if (modFormat == FORMAT_HMNT) // finetune in "His Master's NoiseTracker" is different
			h_MOD31.instr[a].fine = (uint8_t)((-h_MOD31.instr[a].fine & 0x1F) >> 1); // one more bit of precision, + inverted

		s->len = 2 * SWAP16(h_MOD31.instr[a].len);
		s->fine = 8 * ((2 * ((h_MOD31.instr[a].fine & 0xF) ^ 8)) - 16);
		s->vol = h_MOD31.instr[a].vol;
		s->repS = 2 * SWAP16(h_MOD31.instr[a].repS);
		s->repL = 2 * SWAP16(h_MOD31.instr[a].repL);

		if (s->vol > 64)
			s->vol = 64;

		if (s->repL < 2)
			s->repL = 2;

		// fix for poorly converted STK (< v2.5) -> PT/NT modules (FIXME: Worth keeping or not?)
		if (s->repL > 2 && s->repS+s->repL > s->len)
		{
			if ((s->repS>>1)+s->repL <= s->len)
				s->repS >>= 1;
		}

		// fix overflown loop
		if (s->repS+s->repL > s->len)
		{
			if (s->repS >= s->len)
			{
				s->repS = 0;
				s->repL = 0;
			}
			else
			{
				s->repL = s->len - s->repS;
			}
		}

		if (s->repS+s->repL > 2)
			s->typ = 1; // enable loop

		if (!allocateTmpSmpData(s, s->len))
		{
			loaderMsgBox("Not enough memory!");
			return false;
		}

		int32_t bytesRead = (int32_t)fread(s->pek, 1, s->len, f);
		if (bytesRead < s->len)
		{
			int32_t bytesToClear = s->len - bytesRead;
			memset(&s->pek[bytesRead], 0, bytesToClear);
		}

		if (s->typ == 0) // clear repL and repS on non-looping samples...
		{
			s->repL = 0;
			s->repS = 0;
		}
	}

	return true;
}

static uint8_t getModType(uint8_t *numChannels, const char *id)
{
#define IS_ID(s, b) !strncmp(s, b, 4)

	uint8_t modFormat = FORMAT_UNKNOWN;
	*numChannels = 4;

	if (IS_ID("M.K.", id) || IS_ID("M!K!", id) || IS_ID("NSMS", id) ||
		IS_ID("LARD", id) || IS_ID("PATT", id))
	{
		modFormat = FORMAT_MK; // ProTracker or compatible
	}
	else if (isdigit(id[0]) && id[1] == 'C' && id[2] == 'H' && id[3] == 'N') // xCHN
	{
		modFormat = FORMAT_FT2; // FT2 or compatible (multi-channel)
		*numChannels = id[0] - '0';
	}
	else if (isdigit(id[0]) && isdigit(id[1]) && id[2] == 'C' && id[3] == 'H') // xxCH
	{
		modFormat = FORMAT_FT2; // FT2 or compatible (multi-channel)
		*numChannels = ((id[0] - '0') * 10) + (id[1] - '0');
	}
	else if (isdigit(id[0]) && isdigit(id[1]) && id[2] == 'C' && id[3] == 'N') // xxCN (load as xxCH)
	{
		modFormat = FORMAT_FT2; // FT2 or compatible (multi-channel)
		*numChannels = ((id[0] - '0') * 10) + (id[1] - '0');
	}
	else if (IS_ID("FLT4", id))
	{
		modFormat = FORMAT_FLT4; // StarTrekker (4ch modules)
	}
	else if (IS_ID("FLT8", id))
	{
		modFormat = FORMAT_FLT8; // StarTrekker (8ch modules)
		*numChannels = 8;
	}
	else if (IS_ID("N.T.", id))
	{
		modFormat = FORMAT_NT; // NoiseTracker
	}
	else if (IS_ID("M&K!", id) || IS_ID("FEST", id))
	{
		modFormat = FORMAT_HMNT; // His Master's NoiseTracker (special one)
	}

	return modFormat;
}