BOOL CSoundFile::ReadAMF(LPCBYTE lpStream, DWORD dwMemLength)
//-----------------------------------------------------------
{
	AMFFILEHEADER *pfh = (AMFFILEHEADER *)lpStream;
	DWORD dwMemPos;
	
	if ((!lpStream) || (dwMemLength < 2048)) return FALSE;
	if ((!strncmp((LPCTSTR)lpStream, "ASYLUM Music Format V1.0", 25)) && (dwMemLength > 4096))
	{
		UINT numorders, numpats, numsamples;

		dwMemPos = 32;
		numpats = lpStream[dwMemPos+3];
		numorders = lpStream[dwMemPos+4];
		numsamples = 64;
		dwMemPos += 6;
		if ((!numpats) || (numpats > MAX_PATTERNS) || (!numorders)
		 || (numpats*64*32 + 294 + 37*64 >= dwMemLength)) return FALSE;
		m_nType = MOD_TYPE_AMF0;
		m_nChannels = 8;
		m_nInstruments = 0;
		m_nSamples = 31;
		m_nDefaultTempo = 125;
		m_nDefaultSpeed = 6;
		for (UINT iOrd=0; iOrd<MAX_ORDERS; iOrd++)
		{
			Order[iOrd] = (iOrd < numorders) ? lpStream[dwMemPos+iOrd] : 0xFF;
		}
		dwMemPos = 294; // ???
		for (UINT iSmp=0; iSmp<numsamples; iSmp++)
		{
			MODINSTRUMENT *psmp = &Ins[iSmp+1];
			memcpy(m_szNames[iSmp+1], lpStream+dwMemPos, 22);
			psmp->nFineTune = MOD2XMFineTune(lpStream[dwMemPos+22]);
			psmp->nVolume = lpStream[dwMemPos+23];
			psmp->nGlobalVol = 64;
			if (psmp->nVolume > 0x40) psmp->nVolume = 0x40;
			psmp->nVolume <<= 2;
			psmp->nLength = *((LPDWORD)(lpStream+dwMemPos+25));
			psmp->nLoopStart = *((LPDWORD)(lpStream+dwMemPos+29));
			psmp->nLoopEnd = psmp->nLoopStart + *((LPDWORD)(lpStream+dwMemPos+33));
			if ((psmp->nLoopEnd > psmp->nLoopStart) && (psmp->nLoopEnd <= psmp->nLength))
			{
				psmp->uFlags = CHN_LOOP;
			} else
			{
				psmp->nLoopStart = psmp->nLoopEnd = 0;
			}
			if ((psmp->nLength) && (iSmp>31)) m_nSamples = iSmp+1;
			dwMemPos += 37;
		}
		for (UINT iPat=0; iPat<numpats; iPat++)
		{
			MODCOMMAND *p = AllocatePattern(64, m_nChannels);
			if (!p) break;
			Patterns[iPat] = p;
			PatternSize[iPat] = 64;
			const UCHAR *pin = lpStream + dwMemPos;
			for (UINT i=0; i<8*64; i++)
			{
				p->note = 0;

				if (pin[0])
				{
					p->note = pin[0] + 13;
				}
				p->instr = pin[1];
				p->command = pin[2];
				p->param = pin[3];
				if (p->command > 0x0F)
				{
				#ifdef AMFLOG
					Log("0x%02X.0x%02X ?", p->command, p->param);
				#endif
					p->command = 0;
				}
				ConvertModCommand(p);
				pin += 4;
				p++;
			}
			dwMemPos += 64*32;
		}
		// Read samples
		for (UINT iData=0; iData<m_nSamples; iData++)
		{
			MODINSTRUMENT *psmp = &Ins[iData+1];
			if (psmp->nLength)
			{
				dwMemPos += ReadSample(psmp, RS_PCM8S, (LPCSTR)(lpStream+dwMemPos), dwMemLength);
			}
		}
		return TRUE;
	}
	////////////////////////////
	// DSM/AMF
	USHORT *ptracks[MAX_PATTERNS];
	DWORD sampleseekpos[MAX_SAMPLES];

	if ((pfh->szAMF[0] != 'A') || (pfh->szAMF[1] != 'M') || (pfh->szAMF[2] != 'F')
	 || (pfh->version < 10) || (pfh->version > 14) || (!pfh->numtracks)
	 || (!pfh->numorders) || (pfh->numorders > MAX_PATTERNS)
	 || (!pfh->numsamples) || (pfh->numsamples > MAX_SAMPLES)
	 || (pfh->numchannels < 4) || (pfh->numchannels > 32))
		return FALSE;
	memcpy(m_szNames[0], pfh->title, 32);
	dwMemPos = sizeof(AMFFILEHEADER);
	m_nType = MOD_TYPE_AMF;
	m_nChannels = pfh->numchannels;
	m_nSamples = pfh->numsamples;
	m_nInstruments = 0;
	// Setup Channel Pan Positions
	if (pfh->version >= 11)
	{
		signed char *panpos = (signed char *)(lpStream + dwMemPos);
		UINT nchannels = (pfh->version >= 13) ? 32 : 16;
		for (UINT i=0; i<nchannels; i++)
		{
			int pan = (panpos[i] + 64) * 2;
			if (pan < 0) pan = 0;
			if (pan > 256) { pan = 128; ChnSettings[i].dwFlags |= CHN_SURROUND; }
			ChnSettings[i].nPan = pan;
		}
		dwMemPos += nchannels;
	} else
	{
		for (UINT i=0; i<16; i++)
		{
			ChnSettings[i].nPan = (lpStream[dwMemPos+i] & 1) ? 0x30 : 0xD0;
		}
		dwMemPos += 16;
	}
	// Get Tempo/Speed
	m_nDefaultTempo = 125;
	m_nDefaultSpeed = 6;
	if (pfh->version >= 13)
	{
		if (lpStream[dwMemPos] >= 32) m_nDefaultTempo = lpStream[dwMemPos];
		if (lpStream[dwMemPos+1] <= 32) m_nDefaultSpeed = lpStream[dwMemPos+1];
		dwMemPos += 2;
	}
	// Setup sequence list
	for (UINT iOrd=0; iOrd<MAX_ORDERS; iOrd++)
	{
		Order[iOrd] = 0xFF;
		if (iOrd < pfh->numorders)
		{
			Order[iOrd] = iOrd;
			PatternSize[iOrd] = 64;
			if (pfh->version >= 14)
			{
				PatternSize[iOrd] = *(USHORT *)(lpStream+dwMemPos);
				dwMemPos += 2;
			}
			ptracks[iOrd] = (USHORT *)(lpStream+dwMemPos);
			dwMemPos += m_nChannels * sizeof(USHORT);
		}
	}
	if (dwMemPos + m_nSamples * (sizeof(AMFSAMPLE)+8) > dwMemLength) return TRUE;
	// Read Samples
	UINT maxsampleseekpos = 0;
	for (UINT iIns=0; iIns<m_nSamples; iIns++)
	{
		MODINSTRUMENT *pins = &Ins[iIns+1];
		AMFSAMPLE *psh = (AMFSAMPLE *)(lpStream + dwMemPos);

		dwMemPos += sizeof(AMFSAMPLE);
		memcpy(m_szNames[iIns+1], psh->samplename, 32);
		memcpy(pins->name, psh->filename, 13);
		pins->nLength = psh->length;
		pins->nC4Speed = psh->c2spd;
		pins->nGlobalVol = 64;
		pins->nVolume = psh->volume * 4;
		if (pfh->version >= 11)
		{
			pins->nLoopStart = *(DWORD *)(lpStream+dwMemPos);
			pins->nLoopEnd = *(DWORD *)(lpStream+dwMemPos+4);
			dwMemPos += 8;
		} else
		{
			pins->nLoopStart = *(WORD *)(lpStream+dwMemPos);
			pins->nLoopEnd = pins->nLength;
			dwMemPos += 2;
		}
		sampleseekpos[iIns] = 0;
		if ((psh->type) && (psh->offset < dwMemLength-1))
		{
			sampleseekpos[iIns] = psh->offset;
			if (psh->offset > maxsampleseekpos) maxsampleseekpos = psh->offset;
			if ((pins->nLoopEnd > pins->nLoopStart + 2)
			 && (pins->nLoopEnd <= pins->nLength)) pins->uFlags |= CHN_LOOP;
		}
	}
	// Read Track Mapping Table
	USHORT *pTrackMap = (USHORT *)(lpStream+dwMemPos);
	UINT realtrackcnt = 0;
	dwMemPos += pfh->numtracks * sizeof(USHORT);
	for (UINT iTrkMap=0; iTrkMap<pfh->numtracks; iTrkMap++)
	{
		if (realtrackcnt < pTrackMap[iTrkMap]) realtrackcnt = pTrackMap[iTrkMap];
	}
	// Store tracks positions
	BYTE **pTrackData = new BYTE *[realtrackcnt];
	memset(pTrackData, 0, sizeof(pTrackData));
	for (UINT iTrack=0; iTrack<realtrackcnt; iTrack++) if (dwMemPos + 3 <= dwMemLength)
	{
		UINT nTrkSize = *(USHORT *)(lpStream+dwMemPos);
		nTrkSize += (UINT)lpStream[dwMemPos+2] << 16;
		if (dwMemPos + nTrkSize * 3 + 3 <= dwMemLength)
		{
			pTrackData[iTrack] = (BYTE *)(lpStream + dwMemPos);
		}
		dwMemPos += nTrkSize * 3 + 3;
	}
	// Create the patterns from the list of tracks
	for (UINT iPat=0; iPat<pfh->numorders; iPat++)
	{
		MODCOMMAND *p = AllocatePattern(PatternSize[iPat], m_nChannels);
		if (!p) break;
		Patterns[iPat] = p;
		for (UINT iChn=0; iChn<m_nChannels; iChn++)
		{
			UINT nTrack = ptracks[iPat][iChn];
			if ((nTrack) && (nTrack <= pfh->numtracks))
			{
				UINT realtrk = pTrackMap[nTrack-1];
				if (realtrk)
				{
					realtrk--;
					if ((realtrk < realtrackcnt) && (pTrackData[realtrk]))
					{
						AMF_Unpack(p+iChn, pTrackData[realtrk], PatternSize[iPat], m_nChannels);
					}
				}
			}
		}
	}
	delete pTrackData;
	// Read Sample Data
	for (UINT iSeek=1; iSeek<=maxsampleseekpos; iSeek++)
	{
		if (dwMemPos >= dwMemLength) break;
		for (UINT iSmp=0; iSmp<m_nSamples; iSmp++) if (iSeek == sampleseekpos[iSmp])
		{
			MODINSTRUMENT *pins = &Ins[iSmp+1];
			dwMemPos += ReadSample(pins, RS_PCM8U, (LPCSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos);
			break;
		}
	}
	return TRUE;
}
Example #2
0
bool module_renderer::ReadAMS(const uint8_t * const lpStream, const uint32_t dwMemLength)
//-----------------------------------------------------------------------
{
    uint8_t pkinf[MAX_SAMPLES];
    AMSFILEHEADER *pfh = (AMSFILEHEADER *)lpStream;
    uint32_t dwMemPos;
    UINT tmp, tmp2;

    if ((!lpStream) || (dwMemLength < 126)) return false;
    if ((pfh->verhi != 0x01) || (strncmp(pfh->szHeader, "Extreme", 7))
     || (!pfh->patterns) || (!pfh->orders) || (!pfh->samples) || (pfh->samples > MAX_SAMPLES)
     || (pfh->patterns > MAX_PATTERNS) || (pfh->orders > MAX_ORDERS))
    {
        return ReadAMS2(lpStream, dwMemLength);
    }
    dwMemPos = sizeof(AMSFILEHEADER) + pfh->extra;
    if (dwMemPos + pfh->samples * sizeof(AMSSAMPLEHEADER) >= dwMemLength) return false;
    m_nType = MOD_TYPE_AMS;
    m_nInstruments = 0;
    m_nChannels = (pfh->chncfg & 0x1F) + 1;
    m_nSamples = pfh->samples;
    for (UINT nSmp=1; nSmp <= m_nSamples; nSmp++, dwMemPos += sizeof(AMSSAMPLEHEADER))
    {
        AMSSAMPLEHEADER *psh = (AMSSAMPLEHEADER *)(lpStream + dwMemPos);
        modsample_t *pSmp = &Samples[nSmp];
        pSmp->length = psh->length;
        pSmp->loop_start = psh->loopstart;
        pSmp->loop_end = psh->loopend;
        pSmp->global_volume = 64;
        pSmp->default_volume = psh->volume << 1;
        pSmp->c5_samplerate = psh->samplerate;
        pSmp->default_pan = (psh->finetune_and_pan & 0xF0);
        if (pSmp->default_pan < 0x80) pSmp->default_pan += 0x10;
        pSmp->nFineTune = MOD2XMFineTune(psh->finetune_and_pan & 0x0F);
        pSmp->flags = (psh->infobyte & 0x80) ? CHN_16BIT : 0;
        if ((pSmp->loop_end <= pSmp->length) && (pSmp->loop_start+4 <= pSmp->loop_end)) pSmp->flags |= CHN_LOOP;
        pkinf[nSmp] = psh->infobyte;
    }

    // Read Song Name
    if (dwMemPos + 1 >= dwMemLength) return true;
    tmp = lpStream[dwMemPos++];
    if (dwMemPos + tmp + 1 >= dwMemLength) return true;
    tmp2 = (tmp < 32) ? tmp : 31;
    if (tmp2) assign_without_padding(this->song_name, reinterpret_cast<const char *>(lpStream + dwMemPos), tmp2);
    this->song_name.erase(tmp2);
    dwMemPos += tmp;

    // Read sample names
    for (UINT sNam=1; sNam<=m_nSamples; sNam++)
    {
        if (dwMemPos + 32 >= dwMemLength) return true;
        tmp = lpStream[dwMemPos++];
        tmp2 = (tmp < 32) ? tmp : 31;
        if (tmp2) memcpy(m_szNames[sNam], lpStream+dwMemPos, tmp2);
        SpaceToNullStringFixed(m_szNames[sNam], tmp2);
        dwMemPos += tmp;
    }

    // Read Channel names
    for (UINT cNam=0; cNam<m_nChannels; cNam++)
    {
        if (dwMemPos + 32 >= dwMemLength) return true;
        uint8_t chnnamlen = lpStream[dwMemPos++];
        if ((chnnamlen) && (chnnamlen < MAX_CHANNELNAME))
        {
            memcpy(ChnSettings[cNam].szName, lpStream + dwMemPos, chnnamlen);
            SpaceToNullStringFixed(ChnSettings[cNam].szName, chnnamlen);
        }
        dwMemPos += chnnamlen;
    }

    // Read Pattern Names
    for (UINT pNam = 0; pNam < pfh->patterns; pNam++)
    {
        if (dwMemPos + 1 >= dwMemLength) return true;
        tmp = lpStream[dwMemPos++];
        tmp2 = bad_min(tmp, MAX_PATTERNNAME - 1);            // not counting null char
        if (dwMemPos + tmp >= dwMemLength) return true;
        Patterns.Insert(pNam, 64);    // Create pattern now, so that the name won't be overwritten later.
        if(tmp2)
        {
            Patterns[pNam].SetName((char *)(lpStream + dwMemPos), tmp2 + 1);
        }
        dwMemPos += tmp;
    }

    // Read Song Comments
    tmp = *((uint16_t *)(lpStream+dwMemPos));
    dwMemPos += 2;
    if (dwMemPos + tmp >= dwMemLength) return true;
    if (tmp)
    {
        ReadMessage(lpStream + dwMemPos, tmp, leCR, &Convert_AMS_Text_Chars);
    }
    dwMemPos += tmp;

    // Read Order List
    Order.resize(pfh->orders, Order.GetInvalidPatIndex());
    for (UINT iOrd=0; iOrd < pfh->orders; iOrd++, dwMemPos += 2)
    {
        Order[iOrd] = (modplug::tracker::patternindex_t)*((uint16_t *)(lpStream + dwMemPos));
    }

    // Read Patterns
    for (UINT iPat=0; iPat<pfh->patterns; iPat++)
    {
        if (dwMemPos + 4 >= dwMemLength) return true;
        UINT len = *((uint32_t *)(lpStream + dwMemPos));
        dwMemPos += 4;
        if ((len >= dwMemLength) || (dwMemPos + len > dwMemLength)) return true;
        // Pattern has been inserted when reading pattern names
        modplug::tracker::modevent_t* m = Patterns[iPat];
        if (!m) return true;
        const uint8_t *p = lpStream + dwMemPos;
        UINT row = 0, i = 0;
        while ((row < Patterns[iPat].GetNumRows()) && (i+2 < len))
        {
            uint8_t b0 = p[i++];
            uint8_t b1 = p[i++];
            uint8_t b2 = 0;
            UINT ch = b0 & 0x3F;
            // Note+Instr
            if (!(b0 & 0x40))
            {
                b2 = p[i++];
                if (ch < m_nChannels)
                {
                    if (b1 & 0x7F) m[ch].note = (b1 & 0x7F) + 25;
                    m[ch].instr = b2;
                }
                if (b1 & 0x80)
                {
                    b0 |= 0x40;
                    b1 = p[i++];
                }
            }
            // Effect
            if (b0 & 0x40)
            {
            anothercommand:
                if (b1 & 0x40)
                {
                    if (ch < m_nChannels)
                    {
                        m[ch].volcmd = VolCmdVol;
                        m[ch].vol = b1 & 0x3F;
                    }
                } else
                {
                    b2 = p[i++];
                    if (ch < m_nChannels)
                    {
                        UINT cmd = b1 & 0x3F;
                        if (cmd == 0x0C)
                        {
                            m[ch].volcmd = VolCmdVol;
                            m[ch].vol = b2 >> 1;
                        } else
                        if (cmd == 0x0E)
                        {
                            if (!m[ch].command)
                            {
                                UINT command = CmdS3mCmdEx;
                                UINT param = b2;
                                switch(param & 0xF0)
                                {
                                case 0x00:    if (param & 0x08) { param &= 0x07; param |= 0x90; } else {command=param=0;} break;
                                case 0x10:    command = CmdPortaUp; param |= 0xF0; break;
                                case 0x20:    command = CmdPortaDown; param |= 0xF0; break;
                                case 0x30:    param = (param & 0x0F) | 0x10; break;
                                case 0x40:    param = (param & 0x0F) | 0x30; break;
                                case 0x50:    param = (param & 0x0F) | 0x20; break;
                                case 0x60:    param = (param & 0x0F) | 0xB0; break;
                                case 0x70:    param = (param & 0x0F) | 0x40; break;
                                case 0x90:    command = CmdRetrig; param &= 0x0F; break;
                                case 0xA0:    if (param & 0x0F) { command = CmdVolSlide; param = (param << 4) | 0x0F; } else command=param=0; break;
                                case 0xB0:    if (param & 0x0F) { command = CmdVolSlide; param |= 0xF0; } else command=param=0; break;
                                }
                                //XXXih: gross
                                m[ch].command = (modplug::tracker::cmd_t) command;
                                m[ch].param = param;
                            }
                        } else
                        {
                            //XXXih: gross
                            m[ch].command = (modplug::tracker::cmd_t) cmd;
                            m[ch].param = b2;
                            ConvertModCommand(&m[ch]);
                        }
                    }
                }
Example #3
0
bool CSoundFile::ReadDIGI(FileReader &file, ModLoadingFlags loadFlags)
//--------------------------------------------------------------------
{
	file.Rewind();

	DIGIFileHeader fileHeader;
	if(!file.ReadConvertEndianness(fileHeader)
		|| memcmp(fileHeader.signature, "DIGI Booster module\0", 20)
		|| !fileHeader.numChannels
		|| fileHeader.numChannels > 8
		|| fileHeader.lastOrdIndex > 127)
	{
		return false;
	} else if(loadFlags == onlyVerifyHeader)
	{
		return true;
	}

	// Globals
	InitializeGlobals();
	InitializeChannels();

	m_nType = MOD_TYPE_DIGI;
	m_nChannels = fileHeader.numChannels;
	m_nSamples = 31;
	m_nSamplePreAmp = 256 / m_nChannels;
	madeWithTracker = mpt::String::Print("Digi Booster %1.%2", fileHeader.versionInt >> 4, fileHeader.versionInt & 0x0F);

	Order.ReadFromArray(fileHeader.orders, fileHeader.lastOrdIndex + 1);

	// Read sample headers
	for(SAMPLEINDEX smp = 0; smp < 31; smp++)
	{
		ModSample &sample = Samples[smp + 1];
		sample.Initialize(MOD_TYPE_MOD);
		sample.nLength = fileHeader.smpLength[smp];
		sample.nLoopStart = fileHeader.smpLoopStart[smp];
		sample.nLoopEnd = sample.nLoopStart + fileHeader.smpLoopLength[smp];
		if(fileHeader.smpLoopLength[smp])
		{
			sample.uFlags.set(CHN_LOOP);
		}
		sample.SanitizeLoops();
	
		sample.nVolume = std::min(fileHeader.smpVolume[smp], uint8(64)) * 4;
		sample.nFineTune = MOD2XMFineTune(fileHeader.smpFinetune[smp]);
	}

	// Read song + sample names
	file.ReadString<mpt::String::maybeNullTerminated>(songName, 32);
	for(SAMPLEINDEX smp = 1; smp <= 31; smp++)
	{
		file.ReadString<mpt::String::maybeNullTerminated>(m_szNames[smp], 30);
	}

	for(PATTERNINDEX pat = 0; pat <= fileHeader.lastPatIndex; pat++)
	{
		FileReader patternChunk;
		if(fileHeader.packEnable)
		{
			patternChunk = file.ReadChunk(file.ReadUint16BE());
		} else
		{
			patternChunk = file.ReadChunk(4 * 64 * GetNumChannels());
		}

		if(!(loadFlags & loadPatternData) || Patterns.Insert(pat, 64))
		{
			continue;
		}

		if(fileHeader.packEnable)
		{
			std::vector<uint8> eventMask;
			patternChunk.ReadVector(eventMask, 64);

			// Compressed patterns are stored in row-major order...
			for(ROWINDEX row = 0; row < 64; row++)
			{
				PatternRow patRow = Patterns[pat].GetRow(row);
				uint8 bit = 0x80;
				for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++, bit >>= 1)
				{
					if(eventMask[row] & bit)
					{
						ModCommand &m = patRow[chn];
						ReadDIGIPatternEntry(patternChunk, m, *this);
					}
				}
			}
		} else
		{
			// ...but uncompressed patterns are stored in column-major order. WTF!
			for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++)
Example #4
0
static int CSoundFile_ReadAsylum(CSoundFile * that, const uint8_t * lpStream,
    uint32_t dwMemLength)
{
    uint32_t dwMemPos;
    unsigned numorders;
    unsigned numpats;
    unsigned numsamples;
    unsigned i;
    unsigned iPat;

    dwMemPos = 32;
    numpats = lpStream[dwMemPos + 3];
    numorders = lpStream[dwMemPos + 4];
    numsamples = 64;
    dwMemPos += 6;
    if ((!numpats) || (numpats > MAX_PATTERNS) || (!numorders)
	    || (numpats * 64 * 32 + 294 + 37 * 64 >= dwMemLength)) {
	return 0;
    }
    that->m_nType = MOD_TYPE_AMF0;
    that->m_nChannels = 8;
    that->m_nInstruments = 0;
    that->m_nSamples = 31;
    that->m_nDefaultTempo = 125;
    that->m_nDefaultSpeed = 6;
    for (i = 0; i < MAX_ORDERS; i++) {
	that->Order[i] = (i < numorders) ? lpStream[dwMemPos + i] : 0xFF;
	// FIXME: no optimal code
    }
    dwMemPos = 294;			// ???
    for (i = 0; i < numsamples; i++) {
	MODINSTRUMENT *psmp = &that->Ins[i + 1];

	memcpy(that->m_szNames[i + 1], lpStream + dwMemPos, 22);
	psmp->nFineTune = MOD2XMFineTune(lpStream[dwMemPos + 22]);
	psmp->nVolume = lpStream[dwMemPos + 23];
	psmp->nGlobalVol = 64;
	if (psmp->nVolume > 0x40) {
	    psmp->nVolume = 0x40;
	}
	psmp->nVolume <<= 2;
	psmp->nLength = *((uint32_t *) (lpStream + dwMemPos + 25));
	psmp->nLoopStart = *((uint32_t *) (lpStream + dwMemPos + 29));
	psmp->nLoopEnd =
	    psmp->nLoopStart + *((uint32_t *) (lpStream + dwMemPos + 33));
	if ((psmp->nLoopEnd > psmp->nLoopStart)
	    && (psmp->nLoopEnd <= psmp->nLength)) {
	    psmp->uFlags = CHN_LOOP;
	} else {
	    psmp->nLoopStart = psmp->nLoopEnd = 0;
	}
	if ((psmp->nLength) && (i > 31)) {
	    that->m_nSamples = i + 1;
	}
	dwMemPos += 37;
    }
    for (iPat = 0; iPat < numpats; iPat++) {
	MODCOMMAND *p = CSoundFile_AllocatePattern(64, that->m_nChannels);
	const uint8_t *pin;

	if (!p) {
	    break;
	}
	that->Patterns[iPat] = p;
	that->PatternSize[iPat] = 64;
	pin = lpStream + dwMemPos;

	for (i = 0; i < 8 * 64; i++) {
	    p->note = 0;

	    if (pin[0]) {
		p->note = pin[0] + 13;
	    }
	    p->instr = pin[1];
	    p->command = pin[2];
	    p->param = pin[3];
	    if (p->command > 0x0F) {
#ifdef AMFLOG
		Log("0x%02X.0x%02X ?", p->command, p->param);
#endif
		p->command = 0;
	    }
	    CSoundFile_ConvertModCommand(that, p);
	    pin += 4;
	    p++;
	}
	dwMemPos += 64 * 32;
    }
    // Read samples
    for (i = 0; i < that->m_nSamples; i++) {
	MODINSTRUMENT *psmp = &that->Ins[i + 1];

	if (psmp->nLength) {
	    dwMemPos +=
		CSoundFile_ReadSample(that,psmp, RS_PCM8S,
		(const char *)(lpStream + dwMemPos), dwMemLength);
	}
    }
    return 1;
}