/*
 * This source code is public domain.
 *
 * Authors: Olivier Lapicque <olivierl@jps.net>
*/

//////////////////////////////////////////////
// DSIK Internal Format (DSM) module loader //
//////////////////////////////////////////////
#include "libmodplug.h"

#pragma pack(1)

#define DSMID_RIFF	0x46464952	// "RIFF"
#define DSMID_DSMF	0x464d5344	// "DSMF"
#define DSMID_SONG	0x474e4f53	// "SONG"
#define DSMID_INST	0x54534e49	// "INST"
#define DSMID_PATT	0x54544150	// "PATT"


typedef struct DSMNOTE
{
	BYTE note,ins,vol,cmd,inf;
} DSMNOTE;


typedef struct DSMINST
{
	DWORD id_INST;
	DWORD inst_len;
	CHAR filename[13];
	BYTE flags;
	BYTE flags2;
	BYTE volume;
	DWORD length;
	DWORD loopstart;
	DWORD loopend;
	DWORD reserved1;
	WORD c2spd;
	WORD reserved2;
	CHAR samplename[28];
} DSMINST;


typedef struct DSMFILEHEADER
{
	DWORD id_RIFF;	// "RIFF"
	DWORD riff_len;
	DWORD id_DSMF;	// "DSMF"
	DWORD id_SONG;	// "SONG"
	DWORD song_len;
} DSMFILEHEADER;


typedef struct DSMSONG
{
	CHAR songname[28];
	WORD reserved1;
	WORD flags;
	DWORD reserved2;
	WORD numord;
	WORD numsmp;
	WORD numpat;
	WORD numtrk;
	BYTE globalvol;
	BYTE mastervol;
	BYTE speed;
	BYTE bpm;
	BYTE panpos[16];
	BYTE orders[128];
} DSMSONG;

typedef struct DSMPATT
{
	DWORD id_PATT;
	DWORD patt_len;
	BYTE dummy1;
	BYTE dummy2;
} DSMPATT;

#pragma pack()


BOOL CSoundFile_ReadDSM(CSoundFile *_this, LPCBYTE lpStream, DWORD dwMemLength)
//-----------------------------------------------------------
{
	DSMFILEHEADER *pfh = (DSMFILEHEADER *)lpStream;
	DSMSONG *psong;
	DWORD dwMemPos;
	UINT i, nPat, nSmp;

	if ((!lpStream) || (dwMemLength < 1024) || (pfh->id_RIFF != DSMID_RIFF)
	 || (pfh->riff_len + 8 > dwMemLength) || (pfh->riff_len < 1024)
	 || (pfh->id_DSMF != DSMID_DSMF) || (pfh->id_SONG != DSMID_SONG)
	 || (pfh->song_len > dwMemLength)) return FALSE;
	psong = (DSMSONG *)(lpStream + sizeof(DSMFILEHEADER));
	dwMemPos = sizeof(DSMFILEHEADER) + pfh->song_len;
	_this->m_nType = MOD_TYPE_DSM;
	_this->m_nChannels = psong->numtrk;
	if (_this->m_nChannels < 4) _this->m_nChannels = 4;
	if (_this->m_nChannels > 16) _this->m_nChannels = 16;
	_this->m_nSamples = psong->numsmp;
	if (_this->m_nSamples >= MAX_SAMPLES) _this->m_nSamples = MAX_SAMPLES - 1;
	_this->m_nDefaultSpeed = psong->speed;
	_this->m_nDefaultTempo = psong->bpm;
	_this->m_nDefaultGlobalVolume = psong->globalvol << 2;
	if ((!_this->m_nDefaultGlobalVolume) || (_this->m_nDefaultGlobalVolume > 256)) _this->m_nDefaultGlobalVolume = 256;
	_this->m_nSongPreAmp = psong->mastervol & 0x7F;
	for (i=0; i<sizeof(psong->orders); i++)
	{
		_this->Order[i] = (BYTE)((i < psong->numord) ? psong->orders[i] : 0xFF);
	}
	for (i=0; i<16; i++)
	{
		_this->ChnSettings[i].nPan = 0x80;
		if (psong->panpos[i] <= 0x80)
		{
			_this->ChnSettings[i].nPan = psong->panpos[i] << 1;
		}
	}
	nPat = 0;
	nSmp = 1;
	while (dwMemPos < dwMemLength - 8)
	{
		DSMPATT *ppatt = (DSMPATT *)(lpStream + dwMemPos);
		DSMINST *pins = (DSMINST *)(lpStream+dwMemPos);
		// Reading Patterns
		if (ppatt->id_PATT == DSMID_PATT)
		{
			MODCOMMAND *m;
			DWORD dwPos;
			UINT row = 0;
			dwMemPos += 8;
			if (dwMemPos + ppatt->patt_len >= dwMemLength) break;
			dwPos = dwMemPos;
			dwMemPos += ppatt->patt_len;
			m = CSoundFile_AllocatePattern(64, _this->m_nChannels);
			if (!m) break;
			_this->PatternSize[nPat] = 64;
			_this->Patterns[nPat] = m;
			while ((row < 64) && (dwPos + 2 <= dwMemPos))
			{
				UINT flag = lpStream[dwPos++];
				if (flag)
				{
					UINT ch = (flag & 0x0F) % _this->m_nChannels;
					if (flag & 0x80)
					{
						UINT note = lpStream[dwPos++];
						if (note)
						{
							if (note <= 12*9) note += 12;
							m[ch].note = (BYTE)note;
						}
					}
					if (flag & 0x40)
					{
						m[ch].instr = lpStream[dwPos++];
					}
					if (flag & 0x20)
					{
						m[ch].volcmd = VOLCMD_VOLUME;
						m[ch].vol = lpStream[dwPos++];
					}
					if (flag & 0x10)
					{
						UINT command = lpStream[dwPos++];
						UINT param = lpStream[dwPos++];
						switch(command)
						{
						// 4-bit Panning
						case 0x08:
							switch(param & 0xF0)
							{
							case 0x00: param <<= 4; break;
							case 0x10: command = 0x0A; param = (param & 0x0F) << 4; break;
							case 0x20: command = 0x0E; param = (param & 0x0F) | 0xA0; break;
							case 0x30: command = 0x0E; param = (param & 0x0F) | 0x10; break;
							case 0x40: command = 0x0E; param = (param & 0x0F) | 0x20; break;
							default: command = 0;
							}
							break;
						// Portamentos
						case 0x11:
						case 0x12:
							command &= 0x0F;
							break;
						// 3D Sound (?)
						case 0x13:
							command = 'X' - 55;
							param = 0x91;
							break;
						default:
							// Volume + Offset (?)
							command = ((command & 0xF0) == 0x20) ? 0x09 : 0;
						}
						m[ch].command = (BYTE)command;
						m[ch].param = (BYTE)param;
						if (command) CSoundFile_ConvertModCommand(_this, &m[ch]);
					}
				} else
				{
					m += _this->m_nChannels;
					row++;
				}
			}
			nPat++;
		} else
		// Reading Samples
		if ((nSmp <= _this->m_nSamples) && (pins->id_INST == DSMID_INST))
		{
			MODINSTRUMENT *psmp;
			DWORD dwPos;
			UINT smptype;
			if (dwMemPos + pins->inst_len >= dwMemLength - 8) break;
			dwPos = dwMemPos + sizeof(DSMINST);
			dwMemPos += 8 + pins->inst_len;
			psmp = &_this->Ins[nSmp];
			psmp->nGlobalVol = 64;
			psmp->nC4Speed = pins->c2spd;
			psmp->uFlags = (WORD)((pins->flags & 1) ? CHN_LOOP : 0);
			psmp->nLength = pins->length;
			psmp->nLoopStart = pins->loopstart;
			psmp->nLoopEnd = pins->loopend;
			psmp->nVolume = (WORD)(pins->volume << 2);
			if (psmp->nVolume > 256) psmp->nVolume = 256;
			smptype = (pins->flags & 2) ? RS_PCM8S : RS_PCM8U;
			CSoundFile_ReadSample(_this, psmp, smptype, (LPCSTR)(lpStream+dwPos), dwMemLength - dwPos);
			nSmp++;
		} else
		{
			break;
		}
	}
	return TRUE;
}

