/************************/
/**SoundZAP Version 2.3**/
/************************/

/*
 *  This program was compiled with Matthew Dillon's DICE C compiler, which
 *  automatically opens and closes the Amiga libraries as they are needed.
 *  If your compiler does not do this you will have to Add the appropriate
 *  code (or get DICE!!).
 */


#include <exec/types.h>
#include <exec/memory.h>
#include <libraries/dos.h>
#include <stdio.h>
#include <iff/iff.h>
#include <iff/8svx.h>
#include "SoundZAP.h"


void main(int argc, char *argv[])
{
    char comline[33];
    struct options *Opt;


    if (argc<2) GiveUsage();

    if ((Opt=(UBYTE *)AllocMem(sizeof(struct options),MEMF_PUBLIC))==NULL)
        CleanUp(Opt,5);

    Opt->BuffSize  = DEFAULT_SIZE;
    Opt->FlipSign  = FALSE;
    Opt->KillChunk = FALSE;
    Opt->SampRate  = DEFAULT_RATE;
    Opt->IFFOut    = TRUE;
    Opt->InType    = UNKNOWN;
    Opt->MuLaw     = FALSE;
    Opt->SampChk   = TRUE;
    Opt->Data      = NULL;
    Opt->Size      = 0;
    Opt->Bits      = 8;
    strcpy(Opt->inname,"");
    strcpy(Opt->outname,"");

    while (--argc > 0)
    {
        strcpy(comline,argv[argc]);
        if (comline[0]=='-')
            ProcessOpt(Opt,comline);
        else
        {
           strcpy(Opt->outname,Opt->inname);
           strcpy(Opt->inname,comline);
        }
    }
    if (strlen(Opt->inname)==0)
        GiveUsage();

    if (strlen(Opt->outname)==0)
    {
        int l;

        strcpy(Opt->outname,Opt->inname);
        l=strlen(Opt->outname);
        if(Opt->outname[l-4]=='.')
            Opt->outname[l-4]='\0';
        if(Opt->outname[l-3]=='.')
            Opt->outname[l-3]='\0';
        strcat(Opt->outname,".iff");
    }
    printf("Input File: %s\nOutput File: %s\n",Opt->inname,Opt->outname);
    if (Opt->BuffSize!=0) printf("Buffer Size: %d bytes\n",Opt->BuffSize);
    else printf("Allocating maximum buffer size.\n");
    if(!Opt->SampChk)
        printf("Sample Rate: %d samples per second\n",Opt->SampRate);
    AnalyzeData(Opt);
    CleanUp(Opt,0);
}


void GiveUsage()
{
    printf("\nUsage:  SoundZAP [<options>] SOURCE [DESTINATION]\n");
    printf("    (actually, the options can appear anywhere)\n\n");
    printf("    options:    -w         Output RAW Opt->Data\n");
    printf("                -s         Toggle signed/unsigned output\n");
    printf("                -n         Don't create extra chunks in IFF output\n");
    printf("                -f         Assume input Opt->Data is RAW\n");
    printf("                -b<n>      Use a buffer size of n kilobytes\n");
    printf("                            if n==0 then SoundZAP will try to\n");
    printf("                            allocate enough memory to convert\n");
    printf("                            the whole sample in one shot.\n");
    printf("                -r<n>      Change sample rate.\n");
    printf("                            where n is the sample rate or\n");
    printf("                            one of the built in values.\n\n");
    printf("\n\nSee documentation for more info.\n");
    printf("mrc113@psuvm.psu.edu\n\n");
    exit(0);
}

void ProcessOpt(struct options *Opt, char com[])
{
    int len;
    ULONG n;

    switch (com[1])
    {
        case 'w' :  Opt->IFFOut=FALSE;
                    break;

        case 's' :  Opt->FlipSign=TRUE;
                    break;

        case 'n' :  Opt->KillChunk=TRUE;
                    break;

        case 'f' :  Opt->InType=RAW;
                    break;

        case 'b' :  len=strlen(com)-2;
                    if (len==0)
                        CleanUp(Opt,2);
                    Opt->BuffSize=(ULONG)atoi(com+2)*1024;
                    break;

        case 'r' :  len=strlen(com)-2;
                    if (len==0)
                        CleanUp(Opt,3);
                    if (len==1)
                    {
                        switch (com[2])
                        {
                            case '5' :  Opt->SampRate=5696;
                                        break;

                            case '7' :  Opt->SampRate=7596;
                                        break;

                            case '8' :  Opt->SampRate=8000;
                                        break;

                            case '2' :  Opt->SampRate=22790;
                                        break;

                            default  :  Opt->SampRate=11395;
                        }
                    }
                    else
                    {
                        n=(LONG)atoi(com+2);
                        if (n!=0) Opt->SampRate=n;
                        else Opt->SampRate=11395;
                    }
                    Opt->SampChk=FALSE;
                    break;


        default  :  CleanUp(Opt,4);
    }
}

void CleanUp(struct options *Opt, int Error)
{
    if (Opt->Data) FreeMem(Opt->Data,Opt->BuffSize);
    if (Opt) FreeMem(Opt,sizeof(struct options));
    if (in)   Close(in);
    if (out)  Close(out);
    if (Error==0) exit(0);
    else
    {
        printf("%s",ErrorMessages[Error]);
        exit(20);
    }
}

void AnalyzeData(struct options *Opt)
{
    ULONG MagicWord;
    char IsMac[8];

    if ((in=(struct FileHandle *)Open(Opt->inname,MODE_OLDFILE))==NULL)
        CleanUp(Opt,6);

    if(Opt->BuffSize==0)
    {
        Seek(in,0,OFFSET_END);
        Opt->BuffSize=Seek(in,0,OFFSET_BEGINNING);
    }

    if ((Opt->Data=(UBYTE *)AllocMem(Opt->BuffSize,MEMF_PUBLIC))==NULL)
        CleanUp(Opt,5);

    Read(in,&MagicWord,4);
    if (Opt->InType==UNKNOWN)
    {
        if (MagicWord==0x2e736e64)                /* '.snd' */
            ConvertAU(Opt);
        else if (MagicWord==0x464f524d)           /* 'FORM' */
            /*ConvertIFF(Opt);*/ CleanUp(Opt,0);  /* Not supported yet */
        else if (MagicWord==0x43726561)           /* 'Crea' */
            ConvertVOC(Opt);
        else if (MagicWord==0x52494646)           /* 'RIFF' */
            ConvertWAV(Opt);
    }
    Seek(in,65,OFFSET_BEGINNING);
    Read(in,IsMac,8);
    if (!strcmp(IsMac,"FSSDSFX!"))
        ConvertMAC(Opt);
    GuidoCheck(Opt);
}

/*  This routine is a modified version of Guido van Rossum's (guido@cwi.nl)
 *  'whatsound' routine. It guesses the sound file type (signed/unsigned/mu-law)
 *  by checking how the values in the file are distributed. Thanks Guido!!
 */

void GuidoCheck(struct options *Opt)
{
    LONG a,n,sum;
    unsigned long bin[4];
    int x;
    BPTR op=Output();

    if(Opt->FlipSign)
    {
        Seek(in,0,OFFSET_END);
        Opt->Size=Seek(in,0,OFFSET_BEGINNING);
        ConvertRaw(Opt);
    }

    Write(op,"Analyzing Data...",18);
    for (a=0; a<4; a++)
        bin[a]=0;
    do
    {
        n=Read(in,Opt->Data,Opt->BuffSize);
        for(a=0; a<n; a++)
            bin[Opt->Data[a]/64]++;
    }
    while (n==Opt->BuffSize);
    if(bin[2]==0 && bin[3]==0)
        CleanUp(Opt,7);

    x=((bin[0]+bin[3])*100)/(bin[1]+bin[2]);
    if(x>=300)
        Opt->FlipSign=FALSE;
    else if ( x <= 33)
        Opt->FlipSign=TRUE;
    else if ( (x >= 50) && (x <= 200))
        Opt->MuLaw=TRUE;
    printf("Done.\n");
    if(Opt->SampChk && Opt->MuLaw) Opt->SampRate=8000;
    Seek(in,0,OFFSET_END);
    Opt->Size=Seek(in,0,OFFSET_BEGINNING);
    ConvertRaw(Opt);
}


void ConvertRaw(struct options *Opt)
{
    LONG b_read,i,scale,b_written,total;
    int max;
    signed char logs[256];
    BPTR op=Output();


    if((out=(struct FileHandle *)Open(Opt->outname,MODE_NEWFILE))==NULL)
        CleanUp(Opt,8);

    if (Opt->IFFOut)
        WriteIFFStuff(Opt);

    if (Opt->MuLaw)
    {
        Write(op,"Building log tables...",22);
        max=getscale(Opt);
        maketable(logs,max);
        printf("Done.\n");
        if (Opt->InType==AU)
            Seek(in,32,OFFSET_BEGINNING);
        else
            Seek(in,0,OFFSET_BEGINNING);
    }
    Write(op,"Converting...",13);
    scale=Opt->Bits/8;
    total=0;
    do
    {
        b_read=Read(in,Opt->Data,Opt->BuffSize)/scale;

        if(Opt->FlipSign)
            for (i=0; i<b_read; i++)
                Opt->Data[i] ^= 0x80;
        else if(Opt->MuLaw)
            for (i=0; i<b_read; i++)
                Opt->Data[i]=logs[Opt->Data[i]];
        else if(scale!=1)
            for (i=0; i<b_read; i++)
                Opt->Data[i]=Opt->Data[i*scale];

        total+=b_read;
        if (total > Opt->Size)
            b_read-=total-Opt->Size;
        b_written=Write(out,Opt->Data,b_read);
        if(b_written!=b_read) CleanUp(Opt,10);
    }
    while(total < Opt->Size);

    if(Opt->IFFOut)
        Write(out,"\0\0\0",(4-total%4)&3);
    printf("Done.\n");
    CleanUp(Opt,0);
}

void WriteIFFStuff(struct options *Opt)
{
    int i,s;
    ChunkHeader Form, V8Hdr, Body, Auth, Anno;

    Voice8Header V8H = {0,0,32,8363,1,0,Unity};

    if (Opt->KillChunk)
        s=0;
    else s=68;

    Form.ckID = FORM;
    Form.ckSize = 40 + s + Opt->Size + ((4-(Opt->Size%4))&3);

    Write(out,&Form,8);

    i=ID_8SVX;
    Write(out,&i,4);

    if (!Opt->KillChunk)
    {
        Auth.ckID  = ID_AUTH;
        Auth.ckSize = 16;
        Write(out,&Auth,8);
        Write(out,Author,16);

        Anno.ckID = ID_ANNO;
        Anno.ckSize = 36;
        Write(out,&Anno,8);
        Write(out,Annotation,36);
    }

    V8Hdr.ckID = ID_VHDR;
    V8Hdr.ckSize = 20;
    Write(out,&V8Hdr,8);

    V8H.oneShotHiSamples = Opt->Size;
    V8H.samplesPerSec = Opt->SampRate;
    Write(out,&V8H,20);

    Body.ckID = ID_BODY;
    Body.ckSize = Opt->Size + ((4-(Opt->Size%4))&3);
    Write(out,&Body,8);
}


void ConvertVOC(struct options *Opt)
{
    UBYTE c[2];


    if(Opt->SampChk)
    {
        Seek(in,30,OFFSET_BEGINNING);
        Read(in,c,2);
        if(c[1]!=0)
            CleanUp(Opt,9);
        Opt->SampRate=1000000/(256-c[0]);
    }
    Opt->FlipSign=TRUE;
    Seek(in,0,OFFSET_END);
    Opt->Size=Seek(in,0,OFFSET_BEGINNING)-32;
    Seek(in,32,OFFSET_BEGINNING);
    ConvertRaw(Opt);
}

void ConvertWAV(struct options *Opt)
{
    Opt->FlipSign=TRUE;
    Seek(in,0,OFFSET_END);
    Opt->Size=Seek(in,0,OFFSET_BEGINNING)-44;
    Seek(in,44,OFFSET_BEGINNING);
    ConvertRaw(Opt);
}

void ConvertMAC(struct options *Opt)
{
    Opt->FlipSign=TRUE;
    Seek(in,0,OFFSET_END);
    Opt->Size=Seek(in,0,OFFSET_BEGINNING)-668;
    Seek(in,128,OFFSET_BEGINNING);
    ConvertRaw(Opt);
}

/* Thanks to Sean Connolly for sending me info on the .au sound format.*/

void ConvertAU(struct options *Opt)
{
    ULONG i;
    int q;
    AUHeader AUHdr;

    Seek(in,0,OFFSET_END);
    Opt->Size=Seek(in,0,OFFSET_BEGINNING)-32;
    Read(in,&AUHdr,sizeof(AUHdr));
    switch (AUHdr.encoding)
    {
        case 1   :  Opt->MuLaw=TRUE;
                    break;

        case 2   :  Opt->Bits=8;
                    break;

        case 3   :  Opt->Bits=16;
                    Opt->Size/=2;
                    break;

        case 4   :  Opt->Bits=24;
                    Opt->Size/=3;
                    break;

        case 5   :  Opt->Bits=32;
                    Opt->Size/=4;
                    break;

        default  :  CleanUp(Opt,9);
                    break;
    }

    if (Opt->SampChk)
        Opt->SampRate=AUHdr.sample_rate;
    ConvertRaw(Opt);
}


/*--------------------------------------------------------------------------*
 * The following routine was extracted from posting by Brian Foley.         *
 * Brian Foley          email: bfoley@greatlakes.Central.Sun.COM            *
 * Systems Engineer     smail:  1000 Town Center                            *
 * Sun Microsystems             Suite 1700                                  *
 * GreatLakes Region            Southfield, MI 48075   (313) 352-7070       *
 *--------------------------------------------------------------------------*/

int ulaw2linear(unsigned char ulawbyte)
{
        static int exp_lut[8] = { 0, 132, 396, 924, 1980, 4092, 8316, 16764 };
               int sign, exponent, mantissa, sample;

        ulawbyte    = ~ulawbyte;
        sign        =  ulawbyte & 0x80;
        exponent    = (ulawbyte >> 4) & 0x07;
        mantissa    =  ulawbyte & 0x0F;
        sample      = (exp_lut[exponent] + (mantissa << (exponent + 3)));
        if ( sign ) sample = -sample;
        return sample;
}
int getscale(struct options *Opt)
{
        int count, max = 0, i;

        do
        {
            count    = Read(in, Opt->Data, Opt->BuffSize);
            for ( i = 0; i < count; i++ )
                max     = MAX(abs(ulaw2linear(Opt->Data[i])), max);
        }
        while ( count == Opt->BuffSize );

        return max;
}

void maketable(signed char *logs, int max)
{
        int i, c, d;

        for ( i = 0; i < 256; i++ )
        {
                c = ( ulaw2linear(i) * ulaw2linear(0) ) / max;
                d = abs(c) & 0xFF;
                if ( d > 0x7F )
                    if ( c > 0 )
                        logs[i] = (signed char) ( c / 256 + 1 );
                    else
                        logs[i] = (signed char) ( c / 256 - 1 );
                else
                    logs[i] = (signed char) ( c / 256 );
        }
}
