/*
    xm2moddi
    30.8.2004 Marq
    8.8.2016  Safer macros with parentheses
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define U8() fgetc(f)
#define U16() (fgetc(f)+(fgetc(f)<<8))
#define U32() (fgetc(f)+(fgetc(f)<<8)+(fgetc(f)<<16)+(fgetc(f)<<24))

#define M8(x) fputc(x,f)
#define M16(x) fputc((x)>>8,f);fputc((x)&0xff,f)
#define M32(x) fputc(((x)>>24)&255,f);fputc(((x)>>16)&255,f);fputc(((x)>>8)&255,f);\
fputc((x)&255,f)

#define charset(x) (((x)>='!') && ((x)<='z'))

typedef struct ptn_t
{
    unsigned short rows,packed;
    unsigned char *pdata;
    unsigned char *data;
} PTN;

typedef struct sample_t
{
    unsigned len,vol,loop,looplen;
    char *name;
    int finetune;
    signed char *data;
} SMP;

void convptn(PTN *ptn,int chn)
{
    unsigned char *s,*d;
    int n,i,j;

    ptn->data=malloc(chn*5*ptn->rows);
    s=ptn->pdata;
    d=ptn->data;

    memset(d,0,chn*5*ptn->rows);

    if(!ptn->packed)
        return;

    for(n=0;n<ptn->rows;n++)
    {
      for(j=0;j<chn;j++)
      {
        unsigned char b;
        b=*s;
        if(b&0x80) // Packed
        {
            s++;
            if(b&1) *d++=*s++; else *d++;
            if(b&2) *d++=*s++; else *d++;
            if(b&4) *d++=*s++; else *d++;
            if(b&8) *d++=*s++; else *d++;
            if(b&16) *d++=*s++; else *d++;
        }
        else
        {
            for(i=0;i<5;i++)
                *d++=*s++;
        }
      }
    }
}

int main(int argc,char *argv[])
{
    FILE    *f,*d;
    unsigned char  xmid[18],name[21],tname[21],order[256],iname[23];
    unsigned short versio,songlen,restart,chn,ptns,insts,amiga,tempo,bpm,
                   samples;
    unsigned hsize,isize,shsize,ssize;
    static PTN     ptn[256];
    static SMP     smp[128];
    char    tmp;
    int     n,i,j;

    char    *id[]= {"M.K.","6CHN","8CHN","10CH","12CH","14CH","16CH",
                    "18CH","20CH","22CH","24CH","26CH","28CH","30CH","32CH"};
    int     chns[]={4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,-1};

    int     ptable[]={0,
            1712,1616,1524,1440,1356,1280,1208,1140,1076,1016, 960, 906,
            856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453,
            428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226,
            214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113,
            107, 101,  95,  90,  85,  80,  75,  71,  67,  63,  60,  56,
        };

    if(argc!=3)
    {
        fprintf(stderr,"xm2mod source.xm dest.mod\n");
        return(1);
    }
    f=fopen(argv[1],"rb");
    if(f==NULL)
    {
        fprintf(stderr,"File not found\n");
        return(1);
    }

    for(n=0;n<31;n++)
    {
        smp[n].len=smp[n].vol=smp[n].loop=smp[n].looplen=0;
        smp[n].finetune=0;
        smp[n].name="";
    }

    for(n=0;n<17;n++)
        xmid[n]=U8();
    xmid[17]=0;
    if(strcmp(xmid,"Extended Module: "))
    {
        fprintf(stderr,"Not an XM\n",xmid);
        return(1);
    }
    puts("It is an XM indeed");

    for(n=0;n<20;n++)
    {
        tmp=U8();
        name[n]=(charset(tmp))?tmp:' ';
    }
    name[20]=0;
    printf("Name:%s\n",name);

    U8();
    for(n=0;n<20;n++)
    {
        tmp=U8();
        tname[n]=(charset(tmp))?tmp:' ';
    }
    tname[20]=0;
    printf("Tracker name:%s\n",tname);

    versio=U16();
    printf("Version:%x\n",versio);
    hsize=U32();
    printf("Header size:%d\n",hsize);
    songlen=U16();
    printf("Song length:%d\n",songlen);
    restart=U16();
    printf("Restart:%d\n",restart);
    chn=U16();
    printf("Channels:%d\n",chn);
    ptns=U16();
    printf("Patterns:%d\n",ptns);
    insts=U16();
    printf("Instruments:%d\n",insts);
    if(insts>31)
        printf("Warning! Too many instruments.\n");
    if(U16()&1)
        amiga=0;
    else
        amiga=1;
    printf("Amiga periods:%s\n",(amiga)?"yes":"no. You have been warned.");
    tempo=U16();
    printf("Tempo:%d\n",tempo);
    bpm=U16();
    printf("BPM:%d\n",bpm);

    for(n=0;n<256;n++)
        order[n]=U8();

    for(n=0;n<ptns;n++)
    {
        printf("Pattern %d:",n+1);
        U32();  // Skip some BS
        U8();
        ptn[n].rows=U16();
        ptn[n].packed=U16();
        if(ptn[n].packed==0)
            printf("empty pattern. ");
        ptn[n].pdata=malloc(ptn[n].packed);
        for(i=0;i<ptn[n].packed;i++)
            ptn[n].pdata[i]=U8();

        if(ptn[n].rows!=64)
            printf("Warning! Length not 64 rows, ");
        printf("%d rows\n",ptn[n].rows);

        convptn(&ptn[n],chn);
    }

    for(n=0;n<insts;n++)
    {
        printf("Instrument %d:",n+1);

        U32(); /* Instrument length... */
        
        iname[23]=0;
        for(i=0;i<22;i++)
        {
            tmp=U8();
            iname[i]=(charset(tmp))?tmp:' ';
        }
        printf("name:%s, ",iname);
        smp[n].name=strdup(iname);

        U8();
        samples=U16();
        printf("has %d sample(s)",samples);
        if(samples>1)
        {
            printf(" Error: more than 1 sample per instrument\n");
            return(-1);
        }

        puts("");

        if(samples)
        {
            shsize=U32();
            for(j=0;j<96;j++) U8(); // Sample numbers for notes
            for(j=0;j<48*2;j++) U8(); // Panning points
            for(j=0;j<27+7;j++) // Rest of the bs
                U8();
        }

        U32(); // W00t?

        for(i=0;i<samples;i++)
        {
            int slen,loops,loopl;
            unsigned char volo,type;
            signed char finetuna,s8;
            signed short s16;
            char sname[23];

            printf(" Sample %d:",n);
            slen=U32();
            printf("Length %d, ",slen);
            if(slen>=65535*2)
                printf("Warning! Sample too large, ");
            loops=U32();
            loopl=U32();
            volo=U8();
            finetuna=U8();

            smp[n].len=slen>>1;
            smp[n].loop=loops>>1;
            smp[n].looplen=loopl>>1;
            smp[n].vol=volo;
            smp[n].finetune=(finetuna>>4)&0xf;
            smp[n].data=malloc(slen);

            type=U8();
            printf("%d-bit, ",(type&0x10)?16:8);
            U8(); // Panning
            U8(); // Relative note
            U8();
            sname[22]=0;
            for(j=0;j<22;j++)
            {
                tmp=U8();
                sname[j]=(charset(tmp))?tmp:' ';
            }
            printf("Name:%s",sname);
            puts("");

            s8=s16=0;
            for(j=0;j<slen;j++) // Load samples
            {
                if(type&0x10)
                {
                    s16+=(signed short)U16();
                    smp[n].data[j]=s16>>8;
                }
                else
                {
                    s8=U8();
                    s16+=s8;
                    smp[n].data[j]=s16;
                }
            }
        }
    }

    fclose(f);
    f=fopen(argv[2],"wb");
    if(f==NULL)
    {
        printf("File error creating: %s\n",argv[2]);
        return(-1);
    }

    printf("Writing %s...\n",argv[2]);
    for(n=0;n<20;n++)
        M8(name[n]);

    for(n=0;n<31;n++) // Instrument infos
    {
        for(i=0;i<22;i++)
            if(i>=strlen(smp[n].name))
                M8(0);
            else
                M8(smp[n].name[i]);

        M16(smp[n].len);
        M8(smp[n].finetune);
        M8(smp[n].vol);
        M16(smp[n].loop);
        M16(smp[n].looplen);
    }

    M8(songlen);
    M8(127);

    for(n=0;n<128;n++) // Song order
        M8(order[n]);

    // MOD id
    for(n=0;chns[n]!=-1;n++)
        if(chns[n]==chn)
        {
            M8(id[n][0]);
            M8(id[n][1]);
            M8(id[n][2]);
            M8(id[n][3]);
            break;
        }

    /* Write patterns */
    for(n=0;n<ptns && n<64;n++)
    {
        unsigned char *s;
        unsigned char b1,b2,b3,b4;
        unsigned period,note;
        s=ptn[n].data;

        for(i=0;i<64;i++)
        {
            if(i>=ptn[n].rows) // Add extra rows...
            {
                for(j=0;j<4*chn;j++)
                    M8(0);
            }
            else
            {
                for(j=0;j<chn;j++,s+=5)
                {
                    b1=b2=b3=b4=period=0;

                    note=s[0]-24;
                    if(note>=0 && note<=5*12)
                        period=ptable[note];

                    b1=s[1]&0xf0;
                    b1|=period>>8;
                    b2=period&0xff;
                    b3=s[1]<<4;
                    b3|=s[3]&0xf;
                    b4=s[4];

                    M8(b1);M8(b2);M8(b3);M8(b4);
                }
            }

        }
    }

    /* Write samples */
    for(n=0;n<31;n++)
    {
        for(i=0;i<smp[n].len*2 && i<65535*2;i++)
            M8(smp[n].data[i]);
    }

    fclose(f);

    return(0);
}
