diff src/modplug/load_dmf.cxx @ 136:6b5a52635b3b trunk

[svn] - like with so many other things, modplug is now maintained by us.
author nenolod
date Sun, 29 Oct 2006 01:04:52 -0700
parents
children 032053ca08ab 3673c7ec4ea2
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modplug/load_dmf.cxx	Sun Oct 29 01:04:52 2006 -0700
@@ -0,0 +1,606 @@
+/*
+ * This source code is public domain.
+ *
+ * Authors: Olivier Lapicque <olivierl@jps.net>
+*/
+
+///////////////////////////////////////////////////////
+// DMF DELUSION DIGITAL MUSIC FILEFORMAT (X-Tracker) //
+///////////////////////////////////////////////////////
+#include "stdafx.h"
+#include "sndfile.h"
+
+//#define DMFLOG
+
+//#pragma warning(disable:4244)
+
+#pragma pack(1)
+
+typedef struct DMFHEADER
+{
+	DWORD id;				// "DDMF" = 0x464d4444
+	BYTE version;			// 4
+	CHAR trackername[8];	// "XTRACKER"
+	CHAR songname[30];
+	CHAR composer[20];
+	BYTE date[3];
+} DMFHEADER;
+
+typedef struct DMFINFO
+{
+	DWORD id;			// "INFO"
+	DWORD infosize;
+} DMFINFO;
+
+typedef struct DMFSEQU
+{
+	DWORD id;			// "SEQU"
+	DWORD seqsize;
+	WORD loopstart;
+	WORD loopend;
+	WORD sequ[2];
+} DMFSEQU;
+
+typedef struct DMFPATT
+{
+	DWORD id;			// "PATT"
+	DWORD patsize;
+	WORD numpat;		// 1-1024
+	BYTE tracks;
+	BYTE firstpatinfo;
+} DMFPATT;
+
+typedef struct DMFTRACK
+{
+	BYTE tracks;
+	BYTE beat;		// [hi|lo] -> hi=ticks per beat, lo=beats per measure
+	WORD ticks;		// max 512
+	DWORD jmpsize;
+} DMFTRACK;
+
+typedef struct DMFSMPI
+{
+	DWORD id;
+	DWORD size;
+	BYTE samples;
+} DMFSMPI;
+
+typedef struct DMFSAMPLE
+{
+	DWORD len;
+	DWORD loopstart;
+	DWORD loopend;
+	WORD c3speed;
+	BYTE volume;
+	BYTE flags;
+} DMFSAMPLE;
+
+#pragma pack()
+
+
+#ifdef DMFLOG
+extern void Log(LPCSTR s, ...);
+#endif
+
+
+BOOL CSoundFile::ReadDMF(const BYTE *lpStream, DWORD dwMemLength)
+//---------------------------------------------------------------
+{
+	DMFHEADER *pfh = (DMFHEADER *)lpStream;
+	DMFINFO *psi;
+	DMFSEQU *sequ;
+	DWORD dwMemPos;
+	BYTE infobyte[32];
+	BYTE smplflags[MAX_SAMPLES];
+
+	if ((!lpStream) || (dwMemLength < 1024)) return FALSE;
+	if ((pfh->id != 0x464d4444) || (!pfh->version) || (pfh->version & 0xF0)) return FALSE;
+	dwMemPos = 66;
+	memcpy(m_szNames[0], pfh->songname, 30);
+	m_szNames[0][30] = 0;
+	m_nType = MOD_TYPE_DMF;
+	m_nChannels = 0;
+#ifdef DMFLOG
+	Log("DMF version %d: \"%s\": %d bytes (0x%04X)\n", pfh->version, m_szNames[0], dwMemLength, dwMemLength);
+#endif
+	while (dwMemPos + 7 < dwMemLength)
+	{
+		DWORD id = *((LPDWORD)(lpStream+dwMemPos));
+
+		switch(id)
+		{
+		// "INFO"
+		case 0x4f464e49:
+		// "CMSG"
+		case 0x47534d43:
+			psi = (DMFINFO *)(lpStream+dwMemPos);
+			if (id == 0x47534d43) dwMemPos++;
+			if ((psi->infosize > dwMemLength) || (psi->infosize + dwMemPos + 8 > dwMemLength)) goto dmfexit;
+			if ((psi->infosize >= 8) && (!m_lpszSongComments))
+			{
+			    m_lpszSongComments = new char[psi->infosize]; // changed from CHAR
+				if (m_lpszSongComments)
+				{
+					for (UINT i=0; i<psi->infosize-1; i++)
+					{
+						CHAR c = lpStream[dwMemPos+8+i];
+						if ((i % 40) == 39)
+							m_lpszSongComments[i] = 0x0d;
+						else
+							m_lpszSongComments[i] = (c < ' ') ? ' ' : c;
+					}
+					m_lpszSongComments[psi->infosize-1] = 0;
+				}
+			}
+			dwMemPos += psi->infosize + 8 - 1;
+			break;
+
+		// "SEQU"
+		case 0x55514553:
+			sequ = (DMFSEQU *)(lpStream+dwMemPos);
+			if ((sequ->seqsize >= dwMemLength) || (dwMemPos + sequ->seqsize + 12 > dwMemLength)) goto dmfexit;
+			{
+				UINT nseq = sequ->seqsize >> 1;
+				if (nseq >= MAX_ORDERS-1) nseq = MAX_ORDERS-1;
+				if (sequ->loopstart < nseq) m_nRestartPos = sequ->loopstart;
+				for (UINT i=0; i<nseq; i++) Order[i] = (BYTE)sequ->sequ[i];
+			}
+			dwMemPos += sequ->seqsize + 8;
+			break;
+
+		// "PATT"
+		case 0x54544150:
+			if (!m_nChannels)
+			{
+				DMFPATT *patt = (DMFPATT *)(lpStream+dwMemPos);
+				UINT numpat;
+				DWORD dwPos = dwMemPos + 11;
+				if ((patt->patsize >= dwMemLength) || (dwMemPos + patt->patsize + 8 > dwMemLength)) goto dmfexit;
+				numpat = patt->numpat;
+				if (numpat > MAX_PATTERNS) numpat = MAX_PATTERNS;
+				m_nChannels = patt->tracks;
+				if (m_nChannels < patt->firstpatinfo) m_nChannels = patt->firstpatinfo;
+				if (m_nChannels > 32) m_nChannels = 32;
+				if (m_nChannels < 4) m_nChannels = 4;
+				for (UINT npat=0; npat<numpat; npat++)
+				{
+					DMFTRACK *pt = (DMFTRACK *)(lpStream+dwPos);
+				#ifdef DMFLOG
+					Log("Pattern #%d: %d tracks, %d rows\n", npat, pt->tracks, pt->ticks);
+				#endif
+					UINT tracks = pt->tracks;
+					if (tracks > 32) tracks = 32;
+					UINT ticks = pt->ticks;
+					if (ticks > 256) ticks = 256;
+					if (ticks < 16) ticks = 16;
+					dwPos += 8;
+					if ((pt->jmpsize >= dwMemLength) || (dwPos + pt->jmpsize + 4 >= dwMemLength)) break;
+					PatternSize[npat] = (WORD)ticks;
+					MODCOMMAND *m = AllocatePattern(PatternSize[npat], m_nChannels);
+					if (!m) goto dmfexit;
+					Patterns[npat] = m;
+					DWORD d = dwPos;
+					dwPos += pt->jmpsize;
+					UINT ttype = 1;
+					UINT tempo = 125;
+					UINT glbinfobyte = 0;
+					UINT pbeat = (pt->beat & 0xf0) ? pt->beat>>4 : 8;
+					BOOL tempochange = (pt->beat & 0xf0) ? TRUE : FALSE;
+					memset(infobyte, 0, sizeof(infobyte));
+					for (UINT row=0; row<ticks; row++)
+					{
+						MODCOMMAND *p = &m[row*m_nChannels];
+						// Parse track global effects
+						if (!glbinfobyte)
+						{
+							BYTE info = lpStream[d++];
+							BYTE infoval = 0;
+							if ((info & 0x80) && (d < dwPos)) glbinfobyte = lpStream[d++];
+							info &= 0x7f;
+							if ((info) && (d < dwPos)) infoval = lpStream[d++];
+							switch(info)
+							{
+							case 1:	ttype = 0; tempo = infoval; tempochange = TRUE; break;
+							case 2: ttype = 1; tempo = infoval; tempochange = TRUE; break;
+							case 3: pbeat = infoval>>4; tempochange = ttype; break;
+							#ifdef DMFLOG
+							default: if (info) Log("GLB: %02X.%02X\n", info, infoval);
+							#endif
+							}
+						} else
+						{
+							glbinfobyte--;
+						}
+						// Parse channels
+						for (UINT i=0; i<tracks; i++) if (!infobyte[i])
+						{
+							MODCOMMAND cmd = {0,0,0,0,0,0};
+							BYTE info = lpStream[d++];
+							if (info & 0x80) infobyte[i] = lpStream[d++];
+							// Instrument
+							if (info & 0x40)
+							{
+								cmd.instr = lpStream[d++];
+							}
+							// Note
+							if (info & 0x20)
+							{
+								cmd.note = lpStream[d++];
+								if ((cmd.note) && (cmd.note < 0xfe)) cmd.note &= 0x7f;
+								if ((cmd.note) && (cmd.note < 128)) cmd.note += 24;
+							}
+							// Volume
+							if (info & 0x10)
+							{
+								cmd.volcmd = VOLCMD_VOLUME;
+								cmd.vol = (lpStream[d++]+3)>>2;
+							}
+							// Effect 1
+							if (info & 0x08)
+							{
+								BYTE efx = lpStream[d++];
+								BYTE eval = lpStream[d++];
+								switch(efx)
+								{
+								// 1: Key Off
+								case 1: if (!cmd.note) cmd.note = 0xFE; break;
+								// 2: Set Loop
+								// 4: Sample Delay
+								case 4: if (eval&0xe0) { cmd.command = CMD_S3MCMDEX; cmd.param = (eval>>5)|0xD0; } break;
+								// 5: Retrig
+								case 5: if (eval&0xe0) { cmd.command = CMD_RETRIG; cmd.param = (eval>>5); } break;
+								// 6: Offset
+								case 6: cmd.command = CMD_OFFSET; cmd.param = eval; break;
+								#ifdef DMFLOG
+								default: Log("FX1: %02X.%02X\n", efx, eval);
+								#endif
+								}
+							}
+							// Effect 2
+							if (info & 0x04)
+							{
+								BYTE efx = lpStream[d++];
+								BYTE eval = lpStream[d++];
+								switch(efx)
+								{
+								// 1: Finetune
+								case 1: if (eval&0xf0) { cmd.command = CMD_S3MCMDEX; cmd.param = (eval>>4)|0x20; } break;
+								// 2: Note Delay
+								case 2: if (eval&0xe0) { cmd.command = CMD_S3MCMDEX; cmd.param = (eval>>5)|0xD0; } break;
+								// 3: Arpeggio
+								case 3: if (eval) { cmd.command = CMD_ARPEGGIO; cmd.param = eval; } break;
+								// 4: Portamento Up
+								case 4: cmd.command = CMD_PORTAMENTOUP; cmd.param = (eval >= 0xe0) ? 0xdf : eval; break;
+								// 5: Portamento Down
+								case 5: cmd.command = CMD_PORTAMENTODOWN; cmd.param = (eval >= 0xe0) ? 0xdf : eval; break;
+								// 6: Tone Portamento
+								case 6: cmd.command = CMD_TONEPORTAMENTO; cmd.param = eval; break;
+								// 8: Vibrato
+								case 8: cmd.command = CMD_VIBRATO; cmd.param = eval; break;
+								// 12: Note cut
+								case 12: if (eval & 0xe0) { cmd.command = CMD_S3MCMDEX; cmd.param = (eval>>5)|0xc0; }
+										else if (!cmd.note) { cmd.note = 0xfe; } break;
+								#ifdef DMFLOG
+								default: Log("FX2: %02X.%02X\n", efx, eval);
+								#endif
+								}
+							}
+							// Effect 3
+							if (info & 0x02)
+							{
+								BYTE efx = lpStream[d++];
+								BYTE eval = lpStream[d++];
+								switch(efx)
+								{
+								// 1: Vol Slide Up
+								case 1: if (eval == 0xff) break;
+										eval = (eval+3)>>2; if (eval > 0x0f) eval = 0x0f;
+										cmd.command = CMD_VOLUMESLIDE; cmd.param = eval<<4; break;
+								// 2: Vol Slide Down
+								case 2:	if (eval == 0xff) break;
+										eval = (eval+3)>>2; if (eval > 0x0f) eval = 0x0f;
+										cmd.command = CMD_VOLUMESLIDE; cmd.param = eval; break;
+								// 7: Set Pan
+								case 7: if (!cmd.volcmd) { cmd.volcmd = VOLCMD_PANNING; cmd.vol = (eval+3)>>2; }
+										else { cmd.command = CMD_PANNING8; cmd.param = eval; } break;
+								// 8: Pan Slide Left
+								case 8: eval = (eval+3)>>2; if (eval > 0x0f) eval = 0x0f;
+										cmd.command = CMD_PANNINGSLIDE; cmd.param = eval<<4; break;
+								// 9: Pan Slide Right
+								case 9: eval = (eval+3)>>2; if (eval > 0x0f) eval = 0x0f;
+										cmd.command = CMD_PANNINGSLIDE; cmd.param = eval; break;
+								#ifdef DMFLOG
+								default: Log("FX3: %02X.%02X\n", efx, eval);
+								#endif
+
+								}
+							}
+							// Store effect
+							if (i < m_nChannels) p[i] = cmd;
+							if (d > dwPos)
+							{
+							#ifdef DMFLOG
+								Log("Unexpected EOP: row=%d\n", row);
+							#endif
+								break;
+							}
+						} else
+						{
+							infobyte[i]--;
+						}
+
+						// Find free channel for tempo change
+						if (tempochange)
+						{
+							tempochange = FALSE;
+							UINT speed=6, modtempo=tempo;
+							UINT rpm = ((ttype) && (pbeat)) ? tempo*pbeat : (tempo+1)*15;
+							for (speed=30; speed>1; speed--)
+							{
+								modtempo = rpm*speed/24;
+								if (modtempo <= 200) break;
+								if ((speed < 6) && (modtempo < 256)) break;
+							}
+						#ifdef DMFLOG
+							Log("Tempo change: ttype=%d pbeat=%d tempo=%3d -> speed=%d tempo=%d\n",
+								ttype, pbeat, tempo, speed, modtempo);
+						#endif
+							for (UINT ich=0; ich<m_nChannels; ich++) if (!p[ich].command)
+							{
+								if (speed)
+								{
+									p[ich].command = CMD_SPEED;
+									p[ich].param = (BYTE)speed;
+									speed = 0;
+								} else
+								if ((modtempo >= 32) && (modtempo < 256))
+								{
+									p[ich].command = CMD_TEMPO;
+									p[ich].param = (BYTE)modtempo;
+									modtempo = 0;
+								} else
+								{
+									break;
+								}
+							}
+						}
+						if (d >= dwPos) break;
+					}
+				#ifdef DMFLOG
+					Log(" %d/%d bytes remaining\n", dwPos-d, pt->jmpsize);
+				#endif
+					if (dwPos + 8 >= dwMemLength) break;
+				}
+				dwMemPos += patt->patsize + 8;
+			}
+			break;
+
+		// "SMPI": Sample Info
+		case 0x49504d53:
+			{
+				DMFSMPI *pds = (DMFSMPI *)(lpStream+dwMemPos);
+				if (pds->size <= dwMemLength - dwMemPos)
+				{
+					DWORD dwPos = dwMemPos + 9;
+					m_nSamples = pds->samples;
+					if (m_nSamples >= MAX_SAMPLES) m_nSamples = MAX_SAMPLES-1;
+					for (UINT iSmp=1; iSmp<=m_nSamples; iSmp++)
+					{
+						UINT namelen = lpStream[dwPos];
+						smplflags[iSmp] = 0;
+						if (dwPos+namelen+1+sizeof(DMFSAMPLE) > dwMemPos+pds->size+8) break;
+						if (namelen)
+						{
+							UINT rlen = (namelen < 32) ? namelen : 31;
+							memcpy(m_szNames[iSmp], lpStream+dwPos+1, rlen);
+							m_szNames[iSmp][rlen] = 0;
+						}
+						dwPos += namelen + 1;
+						DMFSAMPLE *psh = (DMFSAMPLE *)(lpStream+dwPos);
+						MODINSTRUMENT *psmp = &Ins[iSmp];
+						psmp->nLength = psh->len;
+						psmp->nLoopStart = psh->loopstart;
+						psmp->nLoopEnd = psh->loopend;
+						psmp->nC4Speed = psh->c3speed;
+						psmp->nGlobalVol = 64;
+						psmp->nVolume = (psh->volume) ? ((WORD)psh->volume)+1 : (WORD)256;
+						psmp->uFlags = (psh->flags & 2) ? CHN_16BIT : 0;
+						if (psmp->uFlags & CHN_16BIT) psmp->nLength >>= 1;
+						if (psh->flags & 1) psmp->uFlags |= CHN_LOOP;
+						smplflags[iSmp] = psh->flags;
+						dwPos += (pfh->version < 8) ? 22 : 30;
+					#ifdef DMFLOG
+						Log("SMPI %d/%d: len=%d flags=0x%02X\n", iSmp, m_nSamples, psmp->nLength, psh->flags);
+					#endif
+					}
+				}
+				dwMemPos += pds->size + 8;
+			}
+			break;
+
+		// "SMPD": Sample Data
+		case 0x44504d53:
+			{
+				DWORD dwPos = dwMemPos + 8;
+				UINT ismpd = 0;
+				for (UINT iSmp=1; iSmp<=m_nSamples; iSmp++)
+				{
+					ismpd++;
+					DWORD pksize;
+					if (dwPos + 4 >= dwMemLength)
+					{
+					#ifdef DMFLOG
+						Log("Unexpected EOF at sample %d/%d! (pos=%d)\n", iSmp, m_nSamples, dwPos);
+					#endif
+						break;
+					}
+					pksize = *((LPDWORD)(lpStream+dwPos));
+				#ifdef DMFLOG
+					Log("sample %d: pos=0x%X pksize=%d ", iSmp, dwPos, pksize);
+					Log("len=%d flags=0x%X [%08X]\n", Ins[iSmp].nLength, smplflags[ismpd], *((LPDWORD)(lpStream+dwPos+4)));
+				#endif
+					dwPos += 4;
+					if (pksize > dwMemLength - dwPos)
+					{
+					#ifdef DMFLOG
+						Log("WARNING: pksize=%d, but only %d bytes left\n", pksize, dwMemLength-dwPos);
+					#endif
+						pksize = dwMemLength - dwPos;
+					}
+					if ((pksize) && (iSmp <= m_nSamples))
+					{
+						UINT flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S;
+						if (smplflags[ismpd] & 4) flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_DMF16 : RS_DMF8;
+						ReadSample(&Ins[iSmp], flags, (LPSTR)(lpStream+dwPos), pksize);
+					}
+					dwPos += pksize;
+				}
+				dwMemPos = dwPos;
+			}
+			break;
+
+		// "ENDE": end of file
+		case 0x45444e45:
+			goto dmfexit;
+		
+		// Unrecognized id, or "ENDE" field
+		default:
+			dwMemPos += 4;
+			break;
+		}
+	}
+dmfexit:
+	if (!m_nChannels)
+	{
+		if (!m_nSamples)
+		{
+			m_nType = MOD_TYPE_NONE;
+			return FALSE;
+		}
+		m_nChannels = 4;
+	}
+	return TRUE;
+}
+
+
+///////////////////////////////////////////////////////////////////////
+// DMF Compression
+
+#pragma pack(1)
+
+typedef struct DMF_HNODE
+{
+	short int left, right;
+	BYTE value;
+} DMF_HNODE;
+
+typedef struct DMF_HTREE
+{
+	LPBYTE ibuf, ibufmax;
+	DWORD bitbuf;
+	UINT bitnum;
+	UINT lastnode, nodecount;
+	DMF_HNODE nodes[256];
+} DMF_HTREE;
+
+#pragma pack()
+
+
+// DMF Huffman ReadBits
+BYTE DMFReadBits(DMF_HTREE *tree, UINT nbits)
+//-------------------------------------------
+{
+	BYTE x = 0, bitv = 1;
+	while (nbits--)
+	{
+		if (tree->bitnum)
+		{
+			tree->bitnum--;
+		} else
+		{
+			tree->bitbuf = (tree->ibuf < tree->ibufmax) ? *(tree->ibuf++) : 0;
+			tree->bitnum = 7;
+		}
+		if (tree->bitbuf & 1) x |= bitv;
+		bitv <<= 1;
+		tree->bitbuf >>= 1;
+	}
+	return x;
+}
+
+//
+// tree: [8-bit value][12-bit index][12-bit index] = 32-bit
+//
+
+void DMFNewNode(DMF_HTREE *tree)
+//------------------------------
+{
+	BYTE isleft, isright;
+	UINT actnode;
+
+	actnode = tree->nodecount;
+	if (actnode > 255) return;
+	tree->nodes[actnode].value = DMFReadBits(tree, 7);
+	isleft = DMFReadBits(tree, 1);
+	isright = DMFReadBits(tree, 1);
+	actnode = tree->lastnode;
+	if (actnode > 255) return;
+	tree->nodecount++;
+	tree->lastnode = tree->nodecount;
+	if (isleft)
+	{
+		tree->nodes[actnode].left = tree->lastnode;
+		DMFNewNode(tree);
+	} else
+	{
+		tree->nodes[actnode].left = -1;
+	}
+	tree->lastnode = tree->nodecount;
+	if (isright)
+	{
+		tree->nodes[actnode].right = tree->lastnode;
+		DMFNewNode(tree);
+	} else
+	{
+		tree->nodes[actnode].right = -1;
+	}
+}
+
+
+int DMFUnpack(LPBYTE psample, LPBYTE ibuf, LPBYTE ibufmax, UINT maxlen)
+//----------------------------------------------------------------------
+{
+	DMF_HTREE tree;
+	UINT actnode;
+	BYTE value, sign, delta = 0;
+	
+	memset(&tree, 0, sizeof(tree));
+	tree.ibuf = ibuf;
+	tree.ibufmax = ibufmax;
+	DMFNewNode(&tree);
+	value = 0;
+	for (UINT i=0; i<maxlen; i++)
+	{
+		actnode = 0;
+		sign = DMFReadBits(&tree, 1);
+		do
+		{
+			if (DMFReadBits(&tree, 1))
+				actnode = tree.nodes[actnode].right;
+			else
+				actnode = tree.nodes[actnode].left;
+			if (actnode > 255) break;
+			delta = tree.nodes[actnode].value;
+			if ((tree.ibuf >= tree.ibufmax) && (!tree.bitnum)) break;
+		} while ((tree.nodes[actnode].left >= 0) && (tree.nodes[actnode].right >= 0));
+		if (sign) delta ^= 0xFF;
+		value += delta;
+		psample[i] = (i) ? value : 0;
+	}
+#ifdef DMFLOG
+//	Log("DMFUnpack: %d remaining bytes\n", tree.ibufmax-tree.ibuf);
+#endif
+	return tree.ibuf - ibuf;
+}
+
+