| /* |
| * This source code is public domain. |
| * |
| * Authors: Olivier Lapicque <olivierl@jps.net> |
| */ |
| |
| /////////////////////////////////////////////////// |
| // |
| // AMF module loader |
| // |
| // There is 2 types of AMF files: |
| // - ASYLUM Music Format |
| // - Advanced Music Format(DSM) |
| // |
| /////////////////////////////////////////////////// |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "stdafx.h" |
| #include "sndfile.h" |
| |
| //#define AMFLOG |
| |
| //#pragma warning(disable:4244) |
| |
| #pragma pack(1) |
| |
| typedef struct _AMFFILEHEADER |
| { |
| UCHAR szAMF[3]; |
| UCHAR version; |
| CHAR title[32]; |
| UCHAR numsamples; |
| UCHAR numorders; |
| USHORT numtracks; |
| UCHAR numchannels; |
| } AMFFILEHEADER; |
| |
| typedef struct _AMFSAMPLE |
| { |
| UCHAR type; |
| CHAR samplename[32]; |
| CHAR filename[13]; |
| ULONG offset; |
| ULONG length; |
| USHORT c2spd; |
| UCHAR volume; |
| } AMFSAMPLE; |
| |
| |
| #pragma pack() |
| |
| |
| #ifdef AMFLOG |
| extern void Log(LPCSTR, ...); |
| #endif |
| |
| VOID AMF_Unpack(MODCOMMAND *pPat, const BYTE *pTrack, UINT nRows, UINT nChannels) |
| //------------------------------------------------------------------------------- |
| { |
| UINT lastinstr = 0; |
| UINT nTrkSize = *(USHORT *)pTrack; |
| nTrkSize += (UINT)pTrack[2] << 16; |
| pTrack += 3; |
| while (nTrkSize--) |
| { |
| UINT row = pTrack[0]; |
| UINT cmd = pTrack[1]; |
| UINT arg = pTrack[2]; |
| if (row >= nRows) break; |
| MODCOMMAND *m = pPat + row * nChannels; |
| if (cmd < 0x7F) // note+vol |
| { |
| m->note = cmd+1; |
| if (!m->instr) m->instr = lastinstr; |
| m->volcmd = VOLCMD_VOLUME; |
| m->vol = arg; |
| } else |
| if (cmd == 0x7F) // duplicate row |
| { |
| signed char rdelta = (signed char)arg; |
| int rowsrc = (int)row + (int)rdelta; |
| if ((rowsrc >= 0) && (rowsrc < (int)nRows)) *m = pPat[rowsrc*nChannels]; |
| } else |
| if (cmd == 0x80) // instrument |
| { |
| m->instr = arg+1; |
| lastinstr = m->instr; |
| } else |
| if (cmd == 0x83) // volume |
| { |
| m->volcmd = VOLCMD_VOLUME; |
| m->vol = arg; |
| } else |
| // effect |
| { |
| UINT command = cmd & 0x7F; |
| UINT param = arg; |
| switch(command) |
| { |
| // 0x01: Set Speed |
| case 0x01: command = CMD_SPEED; break; |
| // 0x02: Volume Slide |
| // 0x0A: Tone Porta + Vol Slide |
| // 0x0B: Vibrato + Vol Slide |
| case 0x02: command = CMD_VOLUMESLIDE; |
| case 0x0A: if (command == 0x0A) command = CMD_TONEPORTAVOL; |
| case 0x0B: if (command == 0x0B) command = CMD_VIBRATOVOL; |
| if (param & 0x80) param = (-(signed char)param)&0x0F; |
| else param = (param&0x0F)<<4; |
| break; |
| // 0x04: Porta Up/Down |
| case 0x04: if (param & 0x80) { command = CMD_PORTAMENTOUP; param = -(signed char)param; } |
| else { command = CMD_PORTAMENTODOWN; } break; |
| // 0x06: Tone Portamento |
| case 0x06: command = CMD_TONEPORTAMENTO; break; |
| // 0x07: Tremor |
| case 0x07: command = CMD_TREMOR; break; |
| // 0x08: Arpeggio |
| case 0x08: command = CMD_ARPEGGIO; break; |
| // 0x09: Vibrato |
| case 0x09: command = CMD_VIBRATO; break; |
| // 0x0C: Pattern Break |
| case 0x0C: command = CMD_PATTERNBREAK; break; |
| // 0x0D: Position Jump |
| case 0x0D: command = CMD_POSITIONJUMP; break; |
| // 0x0F: Retrig |
| case 0x0F: command = CMD_RETRIG; break; |
| // 0x10: Offset |
| case 0x10: command = CMD_OFFSET; break; |
| // 0x11: Fine Volume Slide |
| case 0x11: if (param) { command = CMD_VOLUMESLIDE; |
| if (param & 0x80) param = 0xF0|((-(signed char)param)&0x0F); |
| else param = 0x0F|((param&0x0F)<<4); |
| } else command = 0; break; |
| // 0x12: Fine Portamento |
| // 0x16: Extra Fine Portamento |
| case 0x12: |
| case 0x16: if (param) { int mask = (command == 0x16) ? 0xE0 : 0xF0; |
| command = (param & 0x80) ? CMD_PORTAMENTOUP : CMD_PORTAMENTODOWN; |
| if (param & 0x80) param = mask|((-(signed char)param)&0x0F); |
| else param |= mask; |
| } else command = 0; break; |
| // 0x13: Note Delay |
| case 0x13: command = CMD_S3MCMDEX; param = 0xD0|(param & 0x0F); break; |
| // 0x14: Note Cut |
| case 0x14: command = CMD_S3MCMDEX; param = 0xC0|(param & 0x0F); break; |
| // 0x15: Set Tempo |
| case 0x15: command = CMD_TEMPO; break; |
| // 0x17: Panning |
| case 0x17: param = (param+64)&0x7F; |
| if (m->command) { if (!m->volcmd) { m->volcmd = VOLCMD_PANNING; m->vol = param/2; } command = 0; } |
| else { command = CMD_PANNING8; } |
| // Unknown effects |
| default: command = param = 0; |
| } |
| if (command) |
| { |
| m->command = command; |
| m->param = param; |
| } |
| } |
| pTrack += 3; |
| } |
| } |
| |
| |
| |
| 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; |
| } |
| |
| |