diff src/modplug/load_psm.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 b523312b6b0d
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modplug/load_psm.cxx	Sun Oct 29 01:04:52 2006 -0700
@@ -0,0 +1,839 @@
+/*
+ * This source code is public domain.
+ *
+ * Authors: Olivier Lapicque <olivierl@jps.net>
+*/
+
+
+///////////////////////////////////////////////////
+//
+// PSM module loader
+//
+///////////////////////////////////////////////////
+#include "stdafx.h"
+#include "sndfile.h"
+
+//#define PSM_LOG
+
+#define PSM_ID_NEW	0x204d5350
+#define PSM_ID_OLD	0xfe4d5350
+#define IFFID_FILE	0x454c4946
+#define IFFID_TITL	0x4c544954
+#define IFFID_SDFT	0x54464453
+#define IFFID_PBOD	0x444f4250
+#define IFFID_SONG	0x474e4f53
+#define IFFID_PATT	0x54544150
+#define IFFID_DSMP	0x504d5344
+#define IFFID_OPLH	0x484c504f
+
+#pragma pack(1)
+
+typedef struct _PSMCHUNK
+{
+	DWORD id;
+	DWORD len;
+	DWORD listid;
+} PSMCHUNK;
+
+typedef struct _PSMSONGHDR
+{
+	CHAR songname[8];	// "MAINSONG"
+	BYTE reserved1;
+	BYTE reserved2;
+	BYTE channels;
+} PSMSONGHDR;
+
+typedef struct _PSMPATTERN
+{
+	DWORD size;
+	DWORD name;
+	WORD rows;
+	WORD reserved1;
+	BYTE data[4];
+} PSMPATTERN;
+
+typedef struct _PSMSAMPLE
+{
+	BYTE flags;
+	CHAR songname[8];
+	DWORD smpid;
+	CHAR samplename[34];
+	DWORD reserved1;
+	BYTE reserved2;
+	BYTE insno;
+	BYTE reserved3;
+	DWORD length;
+	DWORD loopstart;
+	DWORD loopend;
+	WORD reserved4;
+	BYTE defvol;
+	DWORD reserved5;
+	DWORD samplerate;
+	BYTE reserved6[19];
+} PSMSAMPLE;
+
+#pragma pack()
+
+
+BOOL CSoundFile::ReadPSM(LPCBYTE lpStream, DWORD dwMemLength)
+//-----------------------------------------------------------
+{
+	PSMCHUNK *pfh = (PSMCHUNK *)lpStream;
+	DWORD dwMemPos, dwSongPos;
+	DWORD smpnames[MAX_SAMPLES];
+	DWORD patptrs[MAX_PATTERNS];
+	BYTE samplemap[MAX_SAMPLES];
+	UINT nPatterns;
+
+	// Chunk0: "PSM ",filesize,"FILE"
+	if (dwMemLength < 256) return FALSE;
+	if (pfh->id == PSM_ID_OLD)
+	{
+	#ifdef PSM_LOG
+		Log("Old PSM format not supported\n");
+	#endif
+		return FALSE;
+	}
+	if ((pfh->id != PSM_ID_NEW) || (pfh->len+12 > dwMemLength) || (pfh->listid != IFFID_FILE)) return FALSE;
+	m_nType = MOD_TYPE_PSM;
+	m_nChannels = 16;
+	m_nSamples = 0;
+	nPatterns = 0;
+	dwMemPos = 12;
+	dwSongPos = 0;
+	for (UINT iChPan=0; iChPan<16; iChPan++)
+	{
+		UINT pan = (((iChPan & 3) == 1) || ((iChPan&3)==2)) ? 0xC0 : 0x40;
+		ChnSettings[iChPan].nPan = pan;
+	}
+	while (dwMemPos+8 < dwMemLength)
+	{
+		PSMCHUNK *pchunk = (PSMCHUNK *)(lpStream+dwMemPos);
+		if ((pchunk->len >= dwMemLength - 8) || (dwMemPos + pchunk->len + 8 > dwMemLength)) break;
+		dwMemPos += 8;
+		PUCHAR pdata = (PUCHAR)(lpStream+dwMemPos);
+		ULONG len = pchunk->len;
+		if (len) switch(pchunk->id)
+		{
+		// "TITL": Song title
+		case IFFID_TITL:
+			if (!pdata[0]) { pdata++; len--; }
+			memcpy(m_szNames[0], pdata, (len>31) ? 31 : len);
+			m_szNames[0][31] = 0;
+			break;
+		// "PBOD": Pattern
+		case IFFID_PBOD:
+			if ((len >= 12) && (nPatterns < MAX_PATTERNS))
+			{
+				patptrs[nPatterns++] = dwMemPos-8;
+			}
+			break;
+		// "SONG": Song description
+		case IFFID_SONG:
+			if ((len >= sizeof(PSMSONGHDR)+8) && (!dwSongPos))
+			{
+				dwSongPos = dwMemPos - 8;
+			}
+			break;
+		// "DSMP": Sample Data
+		case IFFID_DSMP:
+			if ((len >= sizeof(PSMSAMPLE)) && (m_nSamples+1 < MAX_SAMPLES))
+			{
+				m_nSamples++;
+				MODINSTRUMENT *pins = &Ins[m_nSamples];
+				PSMSAMPLE *psmp = (PSMSAMPLE *)pdata;
+				smpnames[m_nSamples] = psmp->smpid;
+				memcpy(m_szNames[m_nSamples], psmp->samplename, 31);
+				m_szNames[m_nSamples][31] = 0;
+				samplemap[m_nSamples-1] = (BYTE)m_nSamples;
+				// Init sample
+				pins->nGlobalVol = 0x40;
+				pins->nC4Speed = psmp->samplerate;
+				pins->nLength = psmp->length;
+				pins->nLoopStart = psmp->loopstart;
+				pins->nLoopEnd = psmp->loopend;
+				pins->nPan = 128;
+				pins->nVolume = (psmp->defvol+1) * 2;
+				pins->uFlags = (psmp->flags & 0x80) ? CHN_LOOP : 0;
+				if (pins->nLoopStart > 0) pins->nLoopStart--;
+				// Point to sample data
+				pdata += 0x60;
+				len -= 0x60;
+				// Load sample data
+				if ((pins->nLength > 3) && (len > 3))
+				{
+					ReadSample(pins, RS_PCM8D, (LPCSTR)pdata, len);
+				} else
+				{
+					pins->nLength = 0;
+				}
+			}
+			break;
+	#if 0
+		default:
+			{
+				CHAR s[8], s2[64];
+				*(DWORD *)s = pchunk->id;
+				s[4] = 0;
+				wsprintf(s2, "%s: %4d bytes @ %4d\n", s, pchunk->len, dwMemPos);
+				OutputDebugString(s2);
+			}
+	#endif
+		}
+		dwMemPos += pchunk->len;
+	}
+	// Step #1: convert song structure
+	PSMSONGHDR *pSong = (PSMSONGHDR *)(lpStream+dwSongPos+8);
+	if ((!dwSongPos) || (pSong->channels < 2) || (pSong->channels > 32)) return TRUE;
+	m_nChannels = pSong->channels;
+	// Valid song header -> convert attached chunks
+	{
+		DWORD dwSongEnd = dwSongPos + 8 + *(DWORD *)(lpStream+dwSongPos+4);
+		dwMemPos = dwSongPos + 8 + 11; // sizeof(PSMCHUNK)+sizeof(PSMSONGHDR)
+		while (dwMemPos + 8 < dwSongEnd)
+		{
+			PSMCHUNK *pchunk = (PSMCHUNK *)(lpStream+dwMemPos);
+			dwMemPos += 8;
+			if ((pchunk->len > dwSongEnd) || (dwMemPos + pchunk->len > dwSongEnd)) break;
+			PUCHAR pdata = (PUCHAR)(lpStream+dwMemPos);
+			ULONG len = pchunk->len;
+			switch(pchunk->id)
+			{
+			case IFFID_OPLH:
+				if (len >= 0x20)
+				{
+					UINT pos = len - 3;
+					while (pos > 5)
+					{
+						BOOL bFound = FALSE;
+						pos -= 5;
+						DWORD dwName = *(DWORD *)(pdata+pos);
+						for (UINT i=0; i<nPatterns; i++)
+						{
+							DWORD dwPatName = ((PSMPATTERN *)(lpStream+patptrs[i]+8))->name;
+							if (dwName == dwPatName)
+							{
+								bFound = TRUE;
+								break;
+							}
+						}
+						if ((!bFound) && (pdata[pos+1] > 0) && (pdata[pos+1] <= 0x10)
+						 && (pdata[pos+3] > 0x40) && (pdata[pos+3] < 0xC0))
+						{
+							m_nDefaultSpeed = pdata[pos+1];
+							m_nDefaultTempo = pdata[pos+3];
+							break;
+						}
+					}
+					UINT iOrd = 0;
+					while ((pos+5<len) && (iOrd < MAX_ORDERS))
+					{
+						DWORD dwName = *(DWORD *)(pdata+pos);
+						for (UINT i=0; i<nPatterns; i++)
+						{
+							DWORD dwPatName = ((PSMPATTERN *)(lpStream+patptrs[i]+8))->name;
+							if (dwName == dwPatName)
+							{
+								Order[iOrd++] = i;
+								break;
+							}
+						}
+						pos += 5;
+					}
+				}
+				break;
+			}
+			dwMemPos += pchunk->len;
+		}
+	}
+
+	// Step #2: convert patterns
+	for (UINT nPat=0; nPat<nPatterns; nPat++)
+	{
+		PSMPATTERN *pPsmPat = (PSMPATTERN *)(lpStream+patptrs[nPat]+8);
+		ULONG len = *(DWORD *)(lpStream+patptrs[nPat]+4) - 12;
+		UINT nRows = pPsmPat->rows;
+		if (len > pPsmPat->size) len = pPsmPat->size;
+		if ((nRows < 64) || (nRows > 256)) nRows = 64;
+		PatternSize[nPat] = nRows;
+		if ((Patterns[nPat] = AllocatePattern(nRows, m_nChannels)) == NULL) break;
+		MODCOMMAND *m = Patterns[nPat];
+		BYTE *p = pPsmPat->data;
+		UINT pos = 0;
+		UINT row = 0;
+		UINT oldch = 0;
+		BOOL bNewRow = FALSE;
+	#ifdef PSM_LOG
+		Log("Pattern %d at offset 0x%04X\n", nPat, (DWORD)(p - (BYTE *)lpStream));
+	#endif
+		while ((row < nRows) && (pos+1 < len))
+		{
+			UINT flags = p[pos++];
+			UINT ch = p[pos++];
+			
+		#ifdef PSM_LOG
+			//Log("flags+ch: %02X.%02X\n", flags, ch);
+		#endif
+			if (((flags & 0xf0) == 0x10) && (ch <= oldch) /*&& (!bNewRow)*/)
+			{
+				if ((pos+1<len) && (!(p[pos] & 0x0f)) && (p[pos+1] < m_nChannels))
+				{
+				#ifdef PSM_LOG
+					//if (!nPat) Log("Continuing on new row\n");
+				#endif
+					row++;
+					m += m_nChannels;
+					oldch = ch;
+					continue;
+				}
+			}
+			if ((pos >= len) || (row >= nRows)) break;
+			if (!(flags & 0xf0))
+			{
+			#ifdef PSM_LOG
+				//if (!nPat) Log("EOR(%d): %02X.%02X\n", row, p[pos], p[pos+1]);
+			#endif
+				row++;
+				m += m_nChannels;
+				bNewRow = TRUE;
+				oldch = ch;
+				continue;
+			}
+			bNewRow = FALSE;
+			if (ch >= m_nChannels)
+			{
+			#ifdef PSM_LOG
+				if (!nPat) Log("Invalid channel row=%d (0x%02X.0x%02X)\n", row, flags, ch);
+			#endif
+				ch = 0;
+			}
+			// Note + Instr
+			if ((flags & 0x40) && (pos+1 < len))
+			{
+				UINT note = p[pos++];
+				UINT nins = p[pos++];
+			#ifdef PSM_LOG
+				//if (!nPat) Log("note+ins: %02X.%02X\n", note, nins);
+				if ((!nPat) && (nins >= m_nSamples)) Log("WARNING: invalid instrument number (%d)\n", nins);
+			#endif
+				if ((note) && (note < 0x80)) note = (note>>4)*12+(note&0x0f)+12+1;
+				m[ch].instr = samplemap[nins];
+				m[ch].note = note;
+			}
+			// Volume
+			if ((flags & 0x20) && (pos < len))
+			{
+				m[ch].volcmd = VOLCMD_VOLUME;
+				m[ch].vol = p[pos++] / 2;
+			}
+			// Effect
+			if ((flags & 0x10) && (pos+1 < len))
+			{
+				UINT command = p[pos++];
+				UINT param = p[pos++];
+				// Convert effects
+				switch(command)
+				{
+				// 01: fine volslide up
+				case 0x01:	command = CMD_VOLUMESLIDE; param |= 0x0f; break;
+				// 04: fine volslide down
+				case 0x04:	command = CMD_VOLUMESLIDE; param>>=4; param |= 0xf0; break;
+				// 0C: portamento up
+				case 0x0C:	command = CMD_PORTAMENTOUP; param = (param+1)/2; break;
+				// 0E: portamento down
+				case 0x0E:	command = CMD_PORTAMENTODOWN; param = (param+1)/2; break;
+				// 33: Position Jump
+				case 0x33:	command = CMD_POSITIONJUMP; break;
+				// 34: Pattern break
+				case 0x34:	command = CMD_PATTERNBREAK; break;
+				// 3D: speed
+				case 0x3D:	command = CMD_SPEED; break;
+				// 3E: tempo
+				case 0x3E:	command = CMD_TEMPO; break;
+				// Unknown
+				default:
+				#ifdef PSM_LOG
+					Log("Unknown PSM effect pat=%d row=%d ch=%d: %02X.%02X\n", nPat, row, ch, command, param);
+				#endif
+					command = param = 0;
+				}
+				m[ch].command = (BYTE)command;
+				m[ch].param = (BYTE)param;
+			}
+			oldch = ch;
+		}
+	#ifdef PSM_LOG
+		if (pos < len)
+		{
+			Log("Pattern %d: %d/%d[%d] rows (%d bytes) -> %d bytes left\n", nPat, row, nRows, pPsmPat->rows, pPsmPat->size, len-pos);
+		}
+	#endif
+	}
+
+	// Done (finally!)
+	return TRUE;
+}
+
+
+//////////////////////////////////////////////////////////////
+//
+// PSM Old Format
+//
+
+/*
+
+CONST
+  c_PSM_MaxOrder   = $FF;
+  c_PSM_MaxSample  = $FF;
+  c_PSM_MaxChannel = $0F;
+
+ TYPE
+  PPSM_Header = ^TPSM_Header;
+  TPSM_Header = RECORD
+                 PSM_Sign                   : ARRAY[01..04] OF CHAR; { PSM + #254 }
+                 PSM_SongName               : ARRAY[01..58] OF CHAR;
+                 PSM_Byte00                 : BYTE;
+                 PSM_Byte1A                 : BYTE;
+                 PSM_Unknown00              : BYTE;
+                 PSM_Unknown01              : BYTE;
+                 PSM_Unknown02              : BYTE;
+                 PSM_Speed                  : BYTE;
+                 PSM_Tempo                  : BYTE;
+                 PSM_Unknown03              : BYTE;
+                 PSM_Unknown04              : WORD;
+                 PSM_OrderLength            : WORD;
+                 PSM_PatternNumber          : WORD;
+                 PSM_SampleNumber           : WORD;
+                 PSM_ChannelNumber          : WORD;
+                 PSM_ChannelUsed            : WORD;
+                 PSM_OrderPosition          : LONGINT;
+                 PSM_ChannelSettingPosition : LONGINT;
+                 PSM_PatternPosition        : LONGINT;
+                 PSM_SamplePosition         : LONGINT;
+                { *** perhaps there are some more infos in a larger header,
+                      but i have not decoded it and so it apears here NOT }
+                END;
+
+  PPSM_Sample = ^TPSM_Sample;
+  TPSM_Sample = RECORD
+                 PSM_SampleFileName  : ARRAY[01..12] OF CHAR;
+                 PSM_SampleByte00    : BYTE;
+                 PSM_SampleName      : ARRAY[01..22] OF CHAR;
+                 PSM_SampleUnknown00 : ARRAY[01..02] OF BYTE;
+                 PSM_SamplePosition  : LONGINT;
+                 PSM_SampleUnknown01 : ARRAY[01..04] OF BYTE;
+                 PSM_SampleNumber    : BYTE;
+                 PSM_SampleFlags     : WORD;
+                 PSM_SampleLength    : LONGINT;
+                 PSM_SampleLoopBegin : LONGINT;
+                 PSM_SampleLoopEnd   : LONGINT;
+                 PSM_Unknown03       : BYTE;
+                 PSM_SampleVolume    : BYTE;
+                 PSM_SampleC5Speed   : WORD;
+                END;
+
+  PPSM_SampleList = ^TPSM_SampleList;
+  TPSM_SampleList = ARRAY[01..c_PSM_MaxSample] OF TPSM_Sample;
+
+  PPSM_Order = ^TPSM_Order;
+  TPSM_Order = ARRAY[00..c_PSM_MaxOrder] OF BYTE;
+
+  PPSM_ChannelSettings = ^TPSM_ChannelSettings;
+  TPSM_ChannelSettings = ARRAY[00..c_PSM_MaxChannel] OF BYTE;
+
+ CONST
+  PSM_NotesInPattern   : BYTE = $00;
+  PSM_ChannelInPattern : BYTE = $00;
+
+ CONST
+  c_PSM_SetSpeed = 60;
+
+ FUNCTION PSM_Size(FileName : STRING;FilePosition : LONGINT) : LONGINT;
+  BEGIN
+  END;
+
+ PROCEDURE PSM_UnpackPattern(VAR Source,Destination;PatternLength : WORD);
+  VAR
+   Witz : ARRAY[00..04] OF WORD;
+   I1,I2        : WORD;
+   I3,I4        : WORD;
+   TopicalByte  : ^BYTE;
+   Pattern      : PUnpackedPattern;
+   ChannelP     : BYTE;
+   NoteP        : BYTE;
+   InfoByte     : BYTE;
+   CodeByte     : BYTE;
+   InfoWord     : WORD;
+   Effect       : BYTE;
+   Opperand     : BYTE;
+   Panning      : BYTE;
+   Volume       : BYTE;
+   PrevInfo     : BYTE;
+   InfoIndex    : BYTE;
+  BEGIN
+   Pattern     := @Destination;
+   TopicalByte := @Source;
+  { *** Initialize patttern }
+   FOR I2 := 0 TO c_Maximum_NoteIndex DO
+    FOR I3 := 0 TO c_Maximum_ChannelIndex DO
+     BEGIN
+      Pattern^[I2,I3,c_Pattern_NoteIndex]     := $FF;
+      Pattern^[I2,I3,c_Pattern_SampleIndex]   := $00;
+      Pattern^[I2,I3,c_Pattern_VolumeIndex]   := $FF;
+      Pattern^[I2,I3,c_Pattern_PanningIndex]  := $FF;
+      Pattern^[I2,I3,c_Pattern_EffectIndex]   := $00;
+      Pattern^[I2,I3,c_Pattern_OpperandIndex] := $00;
+     END;
+  { *** Byte-pointer on first pattern-entry }
+   ChannelP    := $00;
+   NoteP       := $00;
+   InfoByte    := $00;
+   PrevInfo    := $00;
+   InfoIndex   := $02;
+  { *** read notes in pattern }
+   PSM_NotesInPattern   := TopicalByte^; INC(TopicalByte); DEC(PatternLength); INC(InfoIndex);
+   PSM_ChannelInPattern := TopicalByte^; INC(TopicalByte); DEC(PatternLength); INC(InfoIndex);
+  { *** unpack pattern }
+   WHILE (INTEGER(PatternLength) > 0) AND (NoteP < c_Maximum_NoteIndex) DO
+    BEGIN
+    { *** Read info-byte }
+     InfoByte := TopicalByte^; INC(TopicalByte); DEC(PatternLength); INC(InfoIndex);
+     IF InfoByte <> $00 THEN
+      BEGIN
+       ChannelP := InfoByte AND $0F;
+       IF InfoByte AND 128 = 128 THEN { note and sample }
+        BEGIN
+        { *** read note }
+         CodeByte := TopicalByte^; INC(TopicalByte); DEC(PatternLength);
+         DEC(CodeByte);
+         CodeByte := CodeByte MOD 12 * 16 + CodeByte DIV 12 + 2;
+         Pattern^[NoteP,ChannelP,c_Pattern_NoteIndex] := CodeByte;
+        { *** read sample }
+         CodeByte := TopicalByte^; INC(TopicalByte); DEC(PatternLength);
+         Pattern^[NoteP,ChannelP,c_Pattern_SampleIndex] := CodeByte;
+        END;
+       IF InfoByte AND 64 = 64 THEN { Volume }
+        BEGIN
+         CodeByte := TopicalByte^; INC(TopicalByte); DEC(PatternLength);
+         Pattern^[NoteP,ChannelP,c_Pattern_VolumeIndex] := CodeByte;
+        END;
+       IF InfoByte AND 32 = 32 THEN { effect AND opperand }
+        BEGIN
+         Effect   := TopicalByte^; INC(TopicalByte); DEC(PatternLength);
+         Opperand := TopicalByte^; INC(TopicalByte); DEC(PatternLength);
+         CASE Effect OF
+          c_PSM_SetSpeed:
+           BEGIN
+            Effect := c_I_Set_Speed;
+           END;
+          ELSE
+           BEGIN
+            Effect   := c_I_NoEffect;
+            Opperand := $00;
+           END;
+         END;
+         Pattern^[NoteP,ChannelP,c_Pattern_EffectIndex]   := Effect;
+         Pattern^[NoteP,ChannelP,c_Pattern_OpperandIndex] := Opperand;
+        END;
+      END ELSE INC(NoteP);
+    END;
+  END;
+
+ PROCEDURE PSM_Load(FileName : STRING;FilePosition : LONGINT;VAR Module : PModule;VAR ErrorCode : WORD);
+ { *** caution : Module has to be inited before!!!! }
+  VAR
+   Header             : PPSM_Header;
+   Sample             : PPSM_SampleList;
+   Order              : PPSM_Order;
+   ChannelSettings    : PPSM_ChannelSettings;
+   MultiPurposeBuffer : PByteArray;
+   PatternBuffer      : PUnpackedPattern;
+   TopicalParaPointer : WORD;
+
+   InFile : FILE;
+   I1,I2  : WORD;
+   I3,I4  : WORD;
+   TempW  : WORD;
+   TempB  : BYTE;
+   TempP  : PByteArray;
+   TempI  : INTEGER;
+  { *** copy-vars for loop-extension }
+   CopySource      : LONGINT;
+   CopyDestination : LONGINT;
+   CopyLength      : LONGINT;
+  BEGIN
+  { *** try to open file }
+   ASSIGN(InFile,FileName);
+{$I-}
+   RESET(InFile,1);
+{$I+}
+   IF IORESULT <> $00 THEN
+    BEGIN
+     EXIT;
+    END;
+{$I-}
+  { *** seek start of module }
+   IF FILESIZE(InFile) < FilePosition THEN
+    BEGIN
+     EXIT;
+    END;
+   SEEK(InFile,FilePosition);
+  { *** look for enough memory for temporary variables }
+   IF MEMAVAIL < SIZEOF(TPSM_Header)       + SIZEOF(TPSM_SampleList) +
+                 SIZEOF(TPSM_Order)        + SIZEOF(TPSM_ChannelSettings) +
+                 SIZEOF(TByteArray)        + SIZEOF(TUnpackedPattern)
+   THEN
+    BEGIN
+     EXIT;
+    END;
+  { *** init dynamic variables }
+   NEW(Header);
+   NEW(Sample);
+   NEW(Order);
+   NEW(ChannelSettings);
+   NEW(MultiPurposeBuffer);
+   NEW(PatternBuffer);
+  { *** read header }
+   BLOCKREAD(InFile,Header^,SIZEOF(TPSM_Header));
+  { *** test if this is a DSM-file }
+   IF NOT ((Header^.PSM_Sign[1] = 'P') AND (Header^.PSM_Sign[2] = 'S')   AND
+           (Header^.PSM_Sign[3] = 'M') AND (Header^.PSM_Sign[4] = #254)) THEN
+    BEGIN
+     ErrorCode := c_NoValidFileFormat;
+     CLOSE(InFile);
+     EXIT;
+    END;
+  { *** read order }
+   SEEK(InFile,FilePosition + Header^.PSM_OrderPosition);
+   BLOCKREAD(InFile,Order^,Header^.PSM_OrderLength);
+  { *** read channelsettings }
+   SEEK(InFile,FilePosition + Header^.PSM_ChannelSettingPosition);
+   BLOCKREAD(InFile,ChannelSettings^,SIZEOF(TPSM_ChannelSettings));
+  { *** read samplelist }
+   SEEK(InFile,FilePosition + Header^.PSM_SamplePosition);
+   BLOCKREAD(InFile,Sample^,Header^.PSM_SampleNumber * SIZEOF(TPSM_Sample));
+  { *** copy header to intern NTMIK-structure }
+   Module^.Module_Sign                 := 'MF';
+   Module^.Module_FileFormatVersion    := $0100;
+   Module^.Module_SampleNumber         := Header^.PSM_SampleNumber;
+   Module^.Module_PatternNumber        := Header^.PSM_PatternNumber;
+   Module^.Module_OrderLength          := Header^.PSM_OrderLength;
+   Module^.Module_ChannelNumber        := Header^.PSM_ChannelNumber+1;
+   Module^.Module_Initial_GlobalVolume := 64;
+   Module^.Module_Initial_MasterVolume := $C0;
+   Module^.Module_Initial_Speed        := Header^.PSM_Speed;
+   Module^.Module_Initial_Tempo        := Header^.PSM_Tempo;
+{ *** paragraph 01 start }
+   Module^.Module_Flags                := c_Module_Flags_ZeroVolume        * BYTE(1) +
+                                          c_Module_Flags_Stereo            * BYTE(1) +
+                                          c_Module_Flags_ForceAmigaLimits  * BYTE(0) +
+                                          c_Module_Flags_Panning           * BYTE(1) +
+                                          c_Module_Flags_Surround          * BYTE(1) +
+                                          c_Module_Flags_QualityMixing     * BYTE(1) +
+                                          c_Module_Flags_FastVolumeSlides  * BYTE(0) +
+                                          c_Module_Flags_SpecialCustomData * BYTE(0) +
+                                          c_Module_Flags_SongName          * BYTE(1);
+   I1 := $01;
+   WHILE (Header^.PSM_SongName[I1] > #00) AND (I1 < c_Module_SongNameLength) DO
+    BEGIN
+     Module^.Module_Name[I1] := Header^.PSM_SongName[I1];
+     INC(I1);
+    END;
+   Module^.Module_Name[c_Module_SongNameLength] := #00;
+  { *** Init channelsettings }
+   FOR I1 := 0 TO c_Maximum_ChannelIndex DO
+    BEGIN
+     IF I1 < Header^.PSM_ChannelUsed THEN
+      BEGIN
+      { *** channel enabled }
+       Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_GlobalVolume := 64;
+       Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Panning      := (ChannelSettings^[I1]) * $08;
+       Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Code         := I1 + $10 * BYTE(ChannelSettings^[I1] > $08) +
+                                             c_ChannelSettings_Code_ChannelEnabled   * BYTE(1) +
+                                             c_ChannelSettings_Code_ChannelDigital   * BYTE(1);
+       Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Controls     :=
+                                             c_ChannelSettings_Controls_EnhancedMode * BYTE(1) +
+                                             c_ChannelSettings_Controls_SurroundMode * BYTE(0);
+      END
+     ELSE
+      BEGIN
+      { *** channel disabled }
+       Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_GlobalVolume := $00;
+       Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Panning      := $00;
+       Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Code         := $00;
+       Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Controls     := $00;
+      END;
+    END;
+  { *** init and copy order }
+   FILLCHAR(Module^.Module_OrderPointer^,c_Maximum_OrderIndex+1,$FF);
+   MOVE(Order^,Module^.Module_OrderPointer^,Header^.PSM_OrderLength);
+  { *** read pattern }
+   SEEK(InFile,FilePosition + Header^.PSM_PatternPosition);
+   NTMIK_LoaderPatternNumber := Header^.PSM_PatternNumber-1;
+   FOR I1 := 0 TO Header^.PSM_PatternNumber-1 DO
+    BEGIN
+     NTMIK_LoadPatternProcedure;
+    { *** read length }
+     BLOCKREAD(InFile,TempW,2);
+    { *** read pattern }
+     BLOCKREAD(InFile,MultiPurposeBuffer^,TempW-2);
+    { *** unpack pattern and set notes per channel to 64 }
+     PSM_UnpackPattern(MultiPurposeBuffer^,PatternBuffer^,TempW);
+     NTMIK_PackPattern(MultiPurposeBuffer^,PatternBuffer^,PSM_NotesInPattern);
+     TempW := WORD(256) * MultiPurposeBuffer^[01] + MultiPurposeBuffer^[00];
+     GETMEM(Module^.Module_PatternPointer^[I1],TempW);
+     MOVE(MultiPurposeBuffer^,Module^.Module_PatternPointer^[I1]^,TempW);
+    { *** next pattern }
+    END;
+  { *** read samples }
+   NTMIK_LoaderSampleNumber := Header^.PSM_SampleNumber;
+   FOR I1 := 1 TO Header^.PSM_SampleNumber DO
+    BEGIN
+     NTMIK_LoadSampleProcedure;
+    { *** get index for sample }
+     I3 := Sample^[I1].PSM_SampleNumber;
+    { *** clip PSM-sample }
+     IF Sample^[I1].PSM_SampleLoopEnd > Sample^[I1].PSM_SampleLength
+     THEN Sample^[I1].PSM_SampleLoopEnd := Sample^[I1].PSM_SampleLength;
+    { *** init intern sample }
+     NEW(Module^.Module_SamplePointer^[I3]);
+     FILLCHAR(Module^.Module_SamplePointer^[I3]^,SIZEOF(TSample),$00);
+     FILLCHAR(Module^.Module_SamplePointer^[I3]^.Sample_SampleName,c_Sample_SampleNameLength,#32);
+     FILLCHAR(Module^.Module_SamplePointer^[I3]^.Sample_FileName,c_Sample_FileNameLength,#32);
+    { *** copy informations to intern sample }
+     I2 := $01;
+     WHILE (Sample^[I1].PSM_SampleName[I2] > #00) AND (I2 < c_Sample_SampleNameLength) DO
+      BEGIN
+       Module^.Module_SamplePointer^[I3]^.Sample_SampleName[I2] := Sample^[I1].PSM_SampleName[I2];
+       INC(I2);
+      END;
+     Module^.Module_SamplePointer^[I3]^.Sample_Sign              := 'DF';
+     Module^.Module_SamplePointer^[I3]^.Sample_FileFormatVersion := $00100;
+     Module^.Module_SamplePointer^[I3]^.Sample_Position          := $00000000;
+     Module^.Module_SamplePointer^[I3]^.Sample_Selector          := $0000;
+     Module^.Module_SamplePointer^[I3]^.Sample_Volume            := Sample^[I1].PSM_SampleVolume;
+     Module^.Module_SamplePointer^[I3]^.Sample_LoopCounter       := $00;
+     Module^.Module_SamplePointer^[I3]^.Sample_C5Speed           := Sample^[I1].PSM_SampleC5Speed;
+     Module^.Module_SamplePointer^[I3]^.Sample_Length            := Sample^[I1].PSM_SampleLength;
+     Module^.Module_SamplePointer^[I3]^.Sample_LoopBegin         := Sample^[I1].PSM_SampleLoopBegin;
+     Module^.Module_SamplePointer^[I3]^.Sample_LoopEnd           := Sample^[I1].PSM_SampleLoopEnd;
+    { *** now it's time for the flags }
+     Module^.Module_SamplePointer^[I3]^.Sample_Flags :=
+                                 c_Sample_Flags_DigitalSample      * BYTE(1) +
+                                 c_Sample_Flags_8BitSample         * BYTE(1) +
+                                 c_Sample_Flags_UnsignedSampleData * BYTE(1) +
+                                 c_Sample_Flags_Packed             * BYTE(0) +
+                                 c_Sample_Flags_LoopCounter        * BYTE(0) +
+                                 c_Sample_Flags_SampleName         * BYTE(1) +
+                                 c_Sample_Flags_LoopActive         *
+                             BYTE(Sample^[I1].PSM_SampleFlags AND (LONGINT(1) SHL 15) = (LONGINT(1) SHL 15));
+    { *** alloc memory for sample-data }
+     E_Getmem(Module^.Module_SamplePointer^[I3]^.Sample_Selector,
+              Module^.Module_SamplePointer^[I3]^.Sample_Position,
+              Module^.Module_SamplePointer^[I3]^.Sample_Length + c_LoopExtensionSize);
+    { *** read out data }
+     EPT(TempP).p_Selector := Module^.Module_SamplePointer^[I3]^.Sample_Selector;
+     EPT(TempP).p_Offset   := $0000;
+     SEEK(InFile,Sample^[I1].PSM_SamplePosition);
+     E_BLOCKREAD(InFile,TempP^,Module^.Module_SamplePointer^[I3]^.Sample_Length);
+    { *** 'coz the samples are signed in a DSM-file -> PC-fy them }
+     IF Module^.Module_SamplePointer^[I3]^.Sample_Length > 4 THEN
+      BEGIN
+       CopyLength := Module^.Module_SamplePointer^[I3]^.Sample_Length;
+      { *** decode sample }
+       ASM
+        DB 066h; MOV CX,WORD PTR CopyLength
+       { *** load sample selector }
+                 MOV ES,WORD PTR TempP[00002h]
+        DB 066h; XOR SI,SI
+        DB 066h; XOR DI,DI
+                 XOR AH,AH
+       { *** conert all bytes }
+                @@MainLoop:
+        DB 026h; DB 067h; LODSB
+                 ADD AL,AH
+                 MOV AH,AL
+        DB 067h; STOSB
+        DB 066h; LOOP @@MainLoop
+       END;
+      { *** make samples unsigned }
+       ASM
+        DB 066h; MOV CX,WORD PTR CopyLength
+       { *** load sample selector }
+                 MOV ES,WORD PTR TempP[00002h]
+        DB 066h; XOR SI,SI
+        DB 066h; XOR DI,DI
+       { *** conert all bytes }
+                @@MainLoop:
+        DB 026h; DB 067h; LODSB
+                 SUB AL,080h
+        DB 067h; STOSB
+        DB 066h; LOOP @@MainLoop
+       END;
+      { *** Create Loop-Extension }
+       IF Module^.Module_SamplePointer^[I3]^.Sample_Flags AND c_Sample_Flags_LoopActive = c_Sample_Flags_LoopActive THEN
+        BEGIN
+         CopySource      := Module^.Module_SamplePointer^[I3]^.Sample_LoopBegin;
+         CopyDestination := Module^.Module_SamplePointer^[I3]^.Sample_LoopEnd;
+         CopyLength      := CopyDestination - CopySource;
+         ASM
+         { *** load sample-selector }
+                   MOV ES,WORD PTR TempP[00002h]
+          DB 066h; MOV DI,WORD PTR CopyDestination
+         { *** calculate number of full sample-loops to copy }
+                   XOR DX,DX
+                   MOV AX,c_LoopExtensionSize
+                   MOV BX,WORD PTR CopyLength
+                   DIV BX
+                   OR AX,AX
+                   JE @@NoFullLoop
+         { *** copy some full-loops (size=bx) }
+                   MOV CX,AX
+                  @@InnerLoop:
+                   PUSH CX
+          DB 066h; MOV SI,WORD PTR CopySource
+                   MOV CX,BX
+          DB 0F3h; DB 026h,067h,0A4h { REP MOVS BYTE PTR ES:[EDI],ES:[ESI] }
+                   POP CX
+                   LOOP @@InnerLoop
+                  @@NoFullLoop:
+         { *** calculate number of rest-bytes to copy }
+          DB 066h; MOV SI,WORD PTR CopySource
+                   MOV CX,DX
+          DB 0F3h; DB 026h,067h,0A4h { REP MOVS BYTE PTR ES:[EDI],ES:[ESI] }
+         END;
+        END
+       ELSE
+        BEGIN
+         CopyDestination := Module^.Module_SamplePointer^[I3]^.Sample_Length;
+         ASM
+         { *** load sample-selector }
+                   MOV ES,WORD PTR TempP[00002h]
+          DB 066h; MOV DI,WORD PTR CopyDestination
+         { *** clear extension }
+                   MOV CX,c_LoopExtensionSize
+                   MOV AL,080h
+          DB 0F3h; DB 067h,0AAh       { REP STOS BYTE PTR ES:[EDI] }
+         END;
+        END;
+      END;
+    { *** next sample }
+    END;
+  { *** init period-ranges }
+   NTMIK_MaximumPeriod := $0000D600 SHR 1;
+   NTMIK_MinimumPeriod := $0000D600 SHR 8;
+  { *** close file }
+   CLOSE(InFile);
+  { *** dispose all dynamic variables }
+   DISPOSE(Header);
+   DISPOSE(Sample);
+   DISPOSE(Order);
+   DISPOSE(ChannelSettings);
+   DISPOSE(MultiPurposeBuffer);
+   DISPOSE(PatternBuffer);
+  { *** set errorcode to noerror }
+   ErrorCode := c_NoError;
+  END;
+
+*/
+