#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "YmMusic.h"
#include "LZH/LZH.H"
static ymu16 ymVolumeTable[16] =
{ 62,161,265,377,580,774,1155,1575,2260,3088,4570,6233,9330,13187,21220,32767};
static void signeSample(ymu8 *ptr,yms32 size)
{
if (size>0)
{
do
{
*ptr++ ^= 0x80;
}
while (--size);
}
}
char *mstrdup(const char *in)
{
const int size = strlen(in)+1;
char *out = (char*)malloc(size);
if (out)
strcpy(out,in);
return out;
}
ymu32 readMotorolaDword(ymu8 **ptr)
{
ymu32 n;
ymu8 *p = *ptr;
n = (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3];
p+=4;
*ptr = p;
return n;
}
ymu16 readMotorolaWord(ymu8 **ptr)
{
ymu16 n;
ymu8 *p = *ptr;
n = (p[0]<<8)|p[1];
p+=2;
*ptr = p;
return n;
}
ymchar *readNtString(ymchar **ptr)
{
ymchar *p;
p = mstrdup(*ptr);
(*ptr) += strlen(*ptr)+1;
return p;
}
yms32 ReadLittleEndian32(ymu8 *pLittle)
{
yms32 v = ( (pLittle[0]<<0) |
(pLittle[1]<<8) |
(pLittle[2]<<16) |
(pLittle[3]<<24));
return v;
}
yms32 ReadBigEndian32(ymu8 *pBig)
{
yms32 v = ( (pBig[0]<<24) |
(pBig[1]<<16) |
(pBig[2]<<8) |
(pBig[3]<<0));
return v;
}
unsigned char *CYmMusic::depackFile(ymu32 checkOriginalSize)
{
lzhHeader_t *pHeader;
ymu8 *pNew;
ymu8 *pSrc;
pHeader = (lzhHeader_t*)pBigMalloc;
if ((pHeader->size==0) || (strncmp(pHeader->id,"-lh5-",5)))
{ return pBigMalloc;
}
fileSize = (ymu32)-1;
if (pHeader->level != 0) { free(pBigMalloc);
pBigMalloc = NULL;
setLastError("LHARC Header must be 0 !");
return NULL;
}
fileSize = ReadLittleEndian32((ymu8*)&pHeader->original);
pNew = (ymu8*)malloc(fileSize);
if (!pNew)
{
setLastError("MALLOC Failed !");
free(pBigMalloc);
pBigMalloc = NULL;
return NULL;
}
pSrc = pBigMalloc+sizeof(lzhHeader_t)+pHeader->name_lenght;
pSrc += 2;
ymu32 packedSize = ReadLittleEndian32((ymu8*)&pHeader->packed);
checkOriginalSize -= ymu32(pSrc - pBigMalloc);
if (packedSize > checkOriginalSize)
packedSize = checkOriginalSize;
if (packedSize <= checkOriginalSize)
{
CLzhDepacker *pDepacker = new CLzhDepacker;
const bool bRet = pDepacker->LzUnpack(pSrc,packedSize,pNew,fileSize);
delete pDepacker;
if (!bRet)
{ setLastError("LH5 Depacking Error !");
free(pNew);
pNew = NULL;
}
}
else
{
setLastError("LH5 Depacking Error !");
free(pNew);
pNew = NULL;
}
free(pBigMalloc);
return pNew;
}
static ymint fileSizeGet(FILE *h)
{
ymint size;
ymint old;
old = ftell(h);
fseek(h,0,SEEK_END);
size = ftell(h);
fseek(h,old,SEEK_SET);
return size;
}
ymbool CYmMusic::deInterleave(void)
{
yms32 nextPlane[32];
ymu8 *pW,*tmpBuff;
yms32 j,k;
if (attrib&A_STREAMINTERLEAVED)
{
tmpBuff = (ymu8*)malloc(nbFrame*streamInc);
if (!tmpBuff)
{
setLastError("Malloc error in deInterleave()\n");
return YMFALSE;
}
for (j=0;j<streamInc;j++) nextPlane[j] = nbFrame*j;
pW = tmpBuff;
for (j=0;j<nextPlane[1];j++)
{
for (k=0;k<streamInc;k++)
{
pW[k] = pDataStream[j + nextPlane[k]];
}
pW += streamInc;
}
free(pBigMalloc);
pBigMalloc = tmpBuff;
pDataStream = tmpBuff;
attrib &= (~A_STREAMINTERLEAVED);
}
return YMTRUE;
}
enum
{
e_YM2a = ('Y' << 24) | ('M' << 16) | ('2' << 8) | ('!'), e_YM3a = ('Y' << 24) | ('M' << 16) | ('3' << 8) | ('!'), e_YM3b = ('Y' << 24) | ('M' << 16) | ('3' << 8) | ('b'), e_YM4a = ('Y' << 24) | ('M' << 16) | ('4' << 8) | ('!'), e_YM5a = ('Y' << 24) | ('M' << 16) | ('5' << 8) | ('!'), e_YM6a = ('Y' << 24) | ('M' << 16) | ('6' << 8) | ('!'), e_MIX1 = ('M' << 24) | ('I' << 16) | ('X' << 8) | ('1'), e_YMT1 = ('Y' << 24) | ('M' << 16) | ('T' << 8) | ('1'), e_YMT2 = ('Y' << 24) | ('M' << 16) | ('T' << 8) | ('2'), };
ymbool CYmMusic::ymDecode(void)
{
ymu8 *pUD;
ymu8 *ptr;
ymint skip;
ymint i;
ymu32 sampleSize;
yms32 tmp;
ymu32 id;
id = ReadBigEndian32((unsigned char*)pBigMalloc);
switch (id)
{
case e_YM2a: songType = YM_V2;
nbFrame = (fileSize-4)/14;
loopFrame = 0;
ymChip.setClock(ATARI_CLOCK);
setPlayerRate(50);
pDataStream = pBigMalloc+4;
streamInc = 14;
nbDrum = 0;
setAttrib(A_STREAMINTERLEAVED|A_TIMECONTROL);
pSongName = mstrdup("Unknown");
pSongAuthor = mstrdup("Unknown");
pSongComment = mstrdup("Converted by Leonard.");
pSongType = mstrdup("YM 2");
pSongPlayer = mstrdup("YM-Chip driver");
break;
case e_YM3a: songType = YM_V3;
nbFrame = (fileSize-4)/14;
loopFrame = 0;
ymChip.setClock(ATARI_CLOCK);
setPlayerRate(50);
pDataStream = pBigMalloc+4;
streamInc = 14;
nbDrum = 0;
setAttrib(A_STREAMINTERLEAVED|A_TIMECONTROL);
pSongName = mstrdup("Unknown");
pSongAuthor = mstrdup("Unknown");
pSongComment = mstrdup("");
pSongType = mstrdup("YM 3");
pSongPlayer = mstrdup("YM-Chip driver");
break;
case e_YM3b: pUD = (ymu8*)(pBigMalloc+fileSize-4);
songType = YM_V3;
nbFrame = (fileSize-4)/14;
loopFrame = ReadLittleEndian32(pUD);
ymChip.setClock(ATARI_CLOCK);
setPlayerRate(50);
pDataStream = pBigMalloc+4;
streamInc = 14;
nbDrum = 0;
setAttrib(A_STREAMINTERLEAVED|A_TIMECONTROL);
pSongName = mstrdup("Unknown");
pSongAuthor = mstrdup("Unknown");
pSongComment = mstrdup("");
pSongType = mstrdup("YM 3b (loop)");
pSongPlayer = mstrdup("YM-Chip driver");
break;
case e_YM4a: setLastError("No more YM4! support. Use YM5! format.");
return YMFALSE;
break;
case e_YM5a: case e_YM6a: if (strncmp((const char*)(pBigMalloc+4),"LeOnArD!",8))
{
setLastError("Not a valid YM format !");
return YMFALSE;
}
ptr = pBigMalloc+12;
nbFrame = readMotorolaDword(&ptr);
setAttrib( readMotorolaDword(&ptr) | A_TIMECONTROL);
nbDrum = readMotorolaWord(&ptr);
ymChip.setClock(readMotorolaDword(&ptr));
setPlayerRate(readMotorolaWord(&ptr));
loopFrame = readMotorolaDword(&ptr);
skip = readMotorolaWord(&ptr);
ptr += skip;
if (nbDrum>0)
{
pDrumTab=(digiDrum_t*)malloc(nbDrum*sizeof(digiDrum_t));
for (i=0;i<nbDrum;i++)
{
pDrumTab[i].size = readMotorolaDword(&ptr);
if (pDrumTab[i].size)
{
pDrumTab[i].pData = (ymu8*)malloc(pDrumTab[i].size);
memcpy(pDrumTab[i].pData,ptr,pDrumTab[i].size);
if (attrib&A_DRUM4BITS)
{
ymu32 j;
ymu8 *pw = pDrumTab[i].pData;
for (j=0;j<pDrumTab[i].size;j++)
{
*pw = ymVolumeTable[(*pw)&15]>>7;
pw++;
}
}
ptr += pDrumTab[i].size;
}
else
{
pDrumTab[i].pData = NULL;
}
}
attrib &= (~A_DRUM4BITS);
}
pSongName = readNtString((char**)&ptr);
pSongAuthor = readNtString((char**)&ptr);
pSongComment = readNtString((char**)&ptr);
songType = YM_V5;
if (id==e_YM6a) {
songType = YM_V6;
pSongType = mstrdup("YM 6");
}
else
{
pSongType = mstrdup("YM 5");
}
pDataStream = ptr;
streamInc = 16;
pSongPlayer = mstrdup("YM-Chip driver");
break;
case e_MIX1:
if (strncmp((const char*)(pBigMalloc+4),"LeOnArD!",8))
{
setLastError("Not a valid YM format !");
return YMFALSE;
}
ptr = pBigMalloc+12;
songType = YM_MIX1;
tmp = readMotorolaDword(&ptr);
setAttrib(0);
if (tmp&1) setAttrib(A_DRUMSIGNED);
sampleSize = readMotorolaDword(&ptr);
nbMixBlock = readMotorolaDword(&ptr);
pMixBlock = (mixBlock_t*)malloc(nbMixBlock*sizeof(mixBlock_t));
for (i=0;i<nbMixBlock;i++)
{ pMixBlock[i].sampleStart = readMotorolaDword(&ptr);
pMixBlock[i].sampleLength = readMotorolaDword(&ptr);
pMixBlock[i].nbRepeat = readMotorolaWord(&ptr);
pMixBlock[i].replayFreq = readMotorolaWord(&ptr);
}
pSongName = readNtString((char**)&ptr);
pSongAuthor = readNtString((char**)&ptr);
pSongComment = readNtString((char**)&ptr);
pBigSampleBuffer = (unsigned char*)malloc(sampleSize);
memcpy(pBigSampleBuffer,ptr,sampleSize);
if (!(attrib&A_DRUMSIGNED))
{
signeSample(pBigSampleBuffer,sampleSize);
setAttrib(A_DRUMSIGNED);
}
setAttrib(getAttrib() | A_TIMECONTROL);
computeTimeInfo();
mixPos = -1; pSongType = mstrdup("MIX1");
pSongPlayer = mstrdup("Digi-Mix driver");
break;
case e_YMT1: case e_YMT2:
if (strncmp((const char*)(pBigMalloc+4),"LeOnArD!",8))
{
setLastError("Not a valid YM format !");
return YMFALSE;
}
ptr = pBigMalloc+12;
songType = YM_TRACKER1;
nbVoice = readMotorolaWord(&ptr);
setPlayerRate(readMotorolaWord(&ptr));
nbFrame= readMotorolaDword(&ptr);
loopFrame = readMotorolaDword(&ptr);
nbDrum = readMotorolaWord(&ptr);
attrib = readMotorolaDword(&ptr);
pSongName = readNtString((char**)&ptr);
pSongAuthor = readNtString((char**)&ptr);
pSongComment = readNtString((char**)&ptr);
if (nbDrum>0)
{
pDrumTab=(digiDrum_t*)malloc(nbDrum*sizeof(digiDrum_t));
for (i=0;i<(ymint)nbDrum;i++)
{
pDrumTab[i].size = readMotorolaWord(&ptr);
pDrumTab[i].repLen = pDrumTab[i].size;
if (e_YMT2 == id) {
pDrumTab[i].repLen = readMotorolaWord(&ptr); readMotorolaWord(&ptr); }
if (pDrumTab[i].repLen>pDrumTab[i].size)
{
pDrumTab[i].repLen = pDrumTab[i].size;
}
if (pDrumTab[i].size)
{
pDrumTab[i].pData = (ymu8*)malloc(pDrumTab[i].size);
memcpy(pDrumTab[i].pData,ptr,pDrumTab[i].size);
ptr += pDrumTab[i].size;
}
else
{
pDrumTab[i].pData = NULL;
}
}
}
ymTrackerFreqShift = 0;
if (e_YMT2 == id) {
ymTrackerFreqShift = (attrib>>28)&15;
attrib &= 0x0fffffff;
pSongType = mstrdup("YM-T2");
}
else
{
pSongType = mstrdup("YM-T1");
}
pDataStream = ptr;
ymChip.setClock(ATARI_CLOCK);
ymTrackerInit(100); streamInc = 16;
setTimeControl(YMTRUE);
pSongPlayer = mstrdup("Universal Tracker");
break;
default:
setLastError("Unknow YM format !");
return YMFALSE;
break;
}
if (!deInterleave())
{
return YMFALSE;
}
return YMTRUE;
}
ymbool CYmMusic::checkCompilerTypes()
{
setLastError("Basic types size are not correct (check ymTypes.h)");
if (1 != sizeof(ymu8)) return YMFALSE;
if (1 != sizeof(yms8)) return YMFALSE;
if (1 != sizeof(ymchar)) return YMFALSE;
if (2 != sizeof(ymu16)) return YMFALSE;
if (2 != sizeof(yms16)) return YMFALSE;
if (4 != sizeof(ymu32)) return YMFALSE;
if (4 != sizeof(yms32)) return YMFALSE;
if (2 != sizeof(ymsample)) return YMFALSE;
#ifdef YM_INTEGER_ONLY
if (8 != sizeof(yms64)) return YMFALSE;
#endif
if (sizeof(ymint) < 4) return YMFALSE;
setLastError("");
return YMTRUE;
}
ymbool CYmMusic::load(const char *fileName)
{
FILE *in;
stop();
unLoad();
if (!checkCompilerTypes())
return YMFALSE;
in = fopen(fileName,"rb");
if (!in)
{
setLastError("File not Found");
return YMFALSE;
}
fileSize = fileSizeGet(in);
pBigMalloc = (unsigned char*)malloc(fileSize);
if (!pBigMalloc)
{
setLastError("MALLOC Error");
fclose(in);
return YMFALSE;
}
if (fread(pBigMalloc,1,fileSize,in)!=(size_t)fileSize)
{
free(pBigMalloc);
setLastError("File is corrupted.");
fclose(in);
return YMFALSE;
}
fclose(in);
pBigMalloc = depackFile(fileSize);
if (!pBigMalloc)
{
return YMFALSE;
}
if (!ymDecode())
{
free(pBigMalloc);
pBigMalloc = NULL;
return YMFALSE;
}
ymChip.reset();
bMusicOk = YMTRUE;
bPause = YMFALSE;
return YMTRUE;
}
ymbool CYmMusic::loadMemory(void *pBlock,ymu32 size)
{
stop();
unLoad();
if (!checkCompilerTypes())
return YMFALSE;
fileSize = size;
pBigMalloc = (unsigned char*)malloc(fileSize);
if (!pBigMalloc)
{
setLastError("MALLOC Error");
return YMFALSE;
}
memcpy(pBigMalloc,pBlock,size);
pBigMalloc = depackFile(size);
if (!pBigMalloc)
{
return YMFALSE;
}
if (!ymDecode())
{
free(pBigMalloc);
pBigMalloc = NULL;
return YMFALSE;
}
ymChip.reset();
bMusicOk = YMTRUE;
bPause = YMFALSE;
return YMTRUE;
}
void myFree(void **pPtr)
{
if (*pPtr) free(*pPtr);
*pPtr = NULL;
}
void CYmMusic::unLoad(void)
{
bMusicOk = YMFALSE;
bPause = YMTRUE;
bMusicOver = YMFALSE;
myFree((void**)&pSongName);
myFree((void**)&pSongAuthor);
myFree((void**)&pSongComment);
myFree((void**)&pSongType);
myFree((void**)&pSongPlayer);
myFree((void**)&pBigMalloc);
if (nbDrum>0)
{
for (ymint i=0;i<nbDrum;i++)
{
myFree((void**)&pDrumTab[i].pData);
}
nbDrum = 0;
myFree((void**)&pDrumTab);
}
myFree((void**)&pBigSampleBuffer);
myFree((void**)&pMixBlock);
myFree((void**)&m_pTimeInfo);
}
void CYmMusic::stop(void)
{
bPause = YMTRUE;
currentFrame = 0;
m_iMusicPosInMs = 0;
m_iMusicPosAccurateSample = 0;
mixPos = -1;
}
void CYmMusic::play(void)
{
bPause = YMFALSE;
}
void CYmMusic::pause(void)
{
bPause = YMTRUE;
}