/*****************************************************************************

    DelfMPEG - MPEG audio player for Delfina DSP
    Copyright (C) 1999-2001  Michael Henke

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*****************************************************************************/


#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/asyncio.h>
#include <proto/timer.h>
#include <proto/reqtools.h>
#include <exec/interrupts.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <exec/libraries.h>
#include <libraries/asyncio.h>
#include <devices/timer.h>
#include <libraries/reqtools.h>
#include <stdlib.h>
#include <math.h>
#include <stdio.h>

static UBYTE version[]="$VER: DelfMPEG 0.7 (Sat 07-Apr-2001)";
static UBYTE template[]=
 "FILES/M,-V=VERBOSE/S,-N=NOPLAY/S,-T=SHOWTAG/S,-NL=NOFASTL/S,-NP=NOFASTP/S,"
 "-F=FRAMEBUF/K/N,-A=ASYNCBUF/K/N,-FF=FFSKIP/K/N,-NT=NOTIMER/S,-M=MONO/S,"
 "-S=STRICT/S,-D=DACRATE/K/N,-VOL=VOLUME/K/N,AREXX/S";

#define DEFAULT_FRAMEBUF    100
#define DEFAULT_ASYNCBUF    128
#define DEFAULT_FFSKIP      10

struct loadbuf {
    ULONG header;
    UWORD framesize, delfcopysize, crc, III_main_data_size;
    UBYTE layer, mode, modext, freq, errprot, br_ind, II_jsbound, II_translate;
    struct loadbuf *next;
    UBYTE data[1728];   /* max: layer II, 384 kbps, 32 kHz -> 1728-4 bytes */
};

struct loadbuf *framebuf0=NULL, *curr_load, *curr_play;
LONG framebuf0size=0;

static ULONG mpg_freq[4]={44100,48000,32000,0};
static UBYTE *mpg_modename[4]={"stereo","j-stereo","dual-ch","single-ch"};
static UBYTE *mpg_layername[3]={"I","II","III"};
static UWORD mpg_bitrate[3][16]=
        { {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, /* I */
          {0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0},   /* II */
          {0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,0} }; /* III */
static UWORD mpg_translate[3][2][16] =
           { { { 0,2,2,2,2,2,2,0,0,0,1,1,1,1,1,0 } ,    /* 44100 stereo */
               { 0,2,2,0,0,0,1,1,1,1,1,1,1,1,1,0 } } ,  /* 44100 mono   */
             { { 0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0 } ,    /* 48000 stereo */
               { 0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0 } } ,  /* 48000 mono   */
             { { 0,3,3,3,3,3,3,0,0,0,1,1,1,1,1,0 } ,    /* 32000 stereo */
               { 0,3,3,0,0,0,1,1,1,1,1,1,1,1,1,0 } } }; /* 32000 mono   */
static UWORD mpg_sblimit[4]={27,30,8,12};

static ULONG ID3v1_TAG;
UBYTE ID3v1_buffer[142];

/** static LONG pow43tab[8206]; **/
#include "MP3_pow43tab.h"
/** static LONG quantab[512]; **/
#include "MP3_quantab.h"

static UWORD bitres_offset, bitres_ok;
static UBYTE bitres_buffer[4096];

#define MPG_MD_STEREO           0
#define MPG_MD_JOINT_STEREO     1
#define MPG_MD_DUAL_CHANNEL     2
#define MPG_MD_MONO             3
#define HDR_MPEG1               0xfff80000
#define HDR_CONSTANT            0x00060c00  /* layer, sampling frequency */
#define ID3V1                   0x54414700  /* TAG */
#define ID3V2                   0x49443300  /* ID3 */
#define XING_FLAG_FRAMES        1
#define XING_FLAG_BYTES         2
#define XING_FLAG_TOC           4

static UBYTE *delfina_name[8]=
    { "Classic","Lite","Pro","1200","Plus","UNKNOWN","UNKNOWN","UNKNOWN" };

extern struct DelfObj DSP56K_PCM;
extern struct DelfObj DSP56K_MP2;
extern struct DelfObj DSP56K_MP3;

#include <libraries/delfina.h>
#include "PCM.h"
#include "MP2.h"
#include "MP3.h"

extern struct ExecBase *SysBase;
struct Library *DelfinaBase=NULL;
struct Library *AsyncIOBase=NULL;
struct Library *TimerBase=NULL;
struct ReqToolsBase *ReqToolsBase=NULL;

struct AsyncFile *file;
struct FileInfoBlock *fib=NULL;
struct TagItem tag_done={TAG_DONE};
UBYTE rtfilename[128]={0};
struct Task *mytask;
ULONG bytes_total, bytes_loaded, buffers_filled, buffers_missed;
ULONG prevheader, currheader, frames_loaded, frames_played;
ULONG setpos_start, setpos_framesize;
ULONG decoder_busy, maindata_err;
struct Interrupt delfint={0};
struct DelfPrg *prg_pcm=NULL, *prg_mp2=NULL, *prg_mp3=NULL;
struct DelfModule *mod_pcm=NULL;
DELFPTR mem_il_mp2=NULL, mem_ip_mp2=NULL, mem_y_mp3_quantab=NULL;
DELFPTR mem_il_mp3=NULL, mem_ip_mp3=NULL, mem_x_mp3_ro=NULL;
ULONG key=0;

UWORD *gb_pt, gb_buf, gb_num;

UBYTE xing_toc[100];
ULONG xingvbr, xing_flags, xing_frames;
ULONG channels,mono,freq,layer,pause,noplay,showtag,rexxmode;
ULONG verbose, ende, havetag, nofastl, nofastp, notimer, forcemono, strict;
LONG framebuf, asyncbuf, ffskip, dacrate, volume;
int rc=0;

/**
*** in arexx.c
**/
/* funtions */
char *initRexx(void);
void cleanupRexx(void);
void handleRexx(void);
/* variables */
extern char *rexxfilename[2];
extern char *portname;
extern WORD rexxstatus, rexxerror;
extern char rexxfiletypebuf[512];
extern ULONG rexxduration, rexxposition;



/*
**
** layer III - bit reservoir handling **
**
*/
UWORD BitReservoir(struct loadbuf *buf)
{
    UWORD main_data_begin, i;
    UBYTE *p0, *p1;
    p1= &buf->data[0];
    main_data_begin= (UWORD)(*p1)<<1 | (UWORD)(*(p1+1))>>7;
    /*
    ** copy side information **
    */
    p0= &bitres_buffer[0];
    for(i=32; i>0; i--) *p0++= *p1++;
    /*
    ** copy previous main_data **
    */
    p0++; /* &bitres_buffer[33] */
    if(bitres_offset>=main_data_begin)
    {
        bitres_ok=1;
        p1=p0+bitres_offset-main_data_begin;
        for(i=main_data_begin; i>0; i--) *p0++= *p1++;
        bitres_offset=main_data_begin;
    }
    else
    {
        bitres_ok=0;            /* not enough data in reservoir */
        p0+=bitres_offset;
    }
    /*
    ** copy current main_data **
    */
    i= mono ? 17 : 32;          /* side info length */
    p1= &buf->data[i];          /* begin of main_data area */
    i= buf->delfcopysize-i;     /* mainslots */
    bitres_offset+= i;
    for(; i>0; i--) *p0++= *p1++;
    return(bitres_ok);
}






/*
**
** Delfina interrupt server **
**
*/
void __saveds IntServer(void)
{
    if(pause)
    {
        /*
        ** mute **
        */
        Delf_Run( prg_pcm->prog+PROG_PCM_MUTE, 0, DRUNF_ASYNCH, 0, 0, 0, 0 );
    }
    else
    {
        if(buffers_filled>0)
        {
            switch(layer)
            {
            case 2:
                if( !Delf_Peek( prg_mp2->ydata+DATY_MP2_BUSY,DMEMF_YDATA ) )
                {
                    /*
                    ** send framedata to Delfina and launch MP2 decoder **
                    */
                    Delf_CopyMem( &curr_play->data[0],
                                  (void*)(prg_mp2->xdata+DATX_MP2_INBUF),
                                  (ULONG)curr_play->delfcopysize,
                                  DCPF_FROM_AMY|DCPF_XDATA|DCPF_24BIT );
                    Delf_Run( prg_mp2->prog+PROG_MP2_DECODE, 0, DRUNF_ASYNCH,
                              mono,
                              Delf_Peek(prg_pcm->ydata+DATY_PCM_BUFPTR,DMEMF_YDATA),
                              (ULONG)curr_play->II_translate,
                              (ULONG)curr_play->II_jsbound );
                    frames_played++;
                    buffers_filled--;
                    curr_play=curr_play->next;
                }
                else decoder_busy++;
                break;
            case 3:
                if( !Delf_Peek( prg_mp3->ydata+DATY_MP3_BUSY,DMEMF_YDATA ) )
                {
                    /*
                    ** send framedata to Delfina and launch MP3 decoder **
                    */
                    if(bitres_ok)
                    {
                        Delf_CopyMem( &bitres_buffer[0],
                                      (void*)(prg_mp3->xdata+DATX_MP3_INBUF),
                                      (ULONG)curr_play->III_main_data_size+33,
                                      DCPF_FROM_AMY|DCPF_XDATA|DCPF_24BIT );
                        Delf_Run( prg_mp3->prog+PROG_MP3_DECODE, 0, DRUNF_ASYNCH,
                                  mono,
                                  Delf_Peek(prg_pcm->ydata+DATY_PCM_BUFPTR,DMEMF_YDATA),
                                  (ULONG)curr_play->modext,
                                  0 );
                    }
                    else maindata_err++;
                    frames_played++;
                    buffers_filled--;
                    curr_play=curr_play->next;
                    if(buffers_filled>0) BitReservoir(curr_play);
                }
                else decoder_busy++;
                break;
            default:
                break;
            }
        }
        else if(frames_loaded>1) buffers_missed++;
    }
}







void initPCM(void)
{
    if(!prg_pcm) return;
    Delf_Run(prg_pcm->prog+PROG_PCM_INIT, 0, 0,
            (ULONG)(forcemono?1:mono),
            (volume>100) ? (volume*0x7fffff)/200 : (volume*0x400000)/100,
            freq, 0 );
}


/*
**
** allocate Delfina resources **
**
*/
int InitDelfina(void)
{
    if(noplay) return(0);
    if(!(prg_pcm=Delf_AddPrg(&DSP56K_PCM)))
    {
        printf("**not enough Delfina memory\n");
        return(0);
    }
    initPCM();
    switch(layer)
    {
        case 2:
            if(!nofastl)
                mem_il_mp2=Delf_AllocMem(INTL_MP2_DATL, DMEMF_LDATA|DMEMF_INTERNAL|DMEMF_ALIGN_64);
            if(!nofastp)
                mem_ip_mp2=Delf_AllocMem(INTP_MP2_PROG, DMEMF_PROG|DMEMF_INTERNAL);
            if(!(prg_mp2=Delf_AddPrg(&DSP56K_MP2)))
            {
                printf("**not enough Delfina memory\n");
                return(0);
            }
            Delf_Run(prg_mp2->prog+PROG_MP2_INIT, 0, 0,
                     mem_il_mp2, mem_ip_mp2, 0, 0);
            if((!mem_il_mp2 && !nofastl) || (!mem_ip_mp2 && !nofastp))
            {
                printf("**warning: not enough internal DSP memory\n"
                       "  (the decoder might run too slowly and produce \"jerky\" sound)\n");
            }
            Delf_Poke(prg_mp2->ydata+DATY_MP2_FORCEMONO, DMEMF_YDATA, forcemono);
            break;
        case 3:
            if(!nofastl)
                mem_il_mp3=Delf_AllocMem(INTL_MP3_DATL, DMEMF_LDATA|DMEMF_INTERNAL|DMEMF_ALIGN_64);
            if(!nofastp)
                mem_ip_mp3=Delf_AllocMem(INTP_MP3_PROG, DMEMF_PROG|DMEMF_INTERNAL);
            mem_x_mp3_ro=Delf_AllocMem(EXTX_MP3_RO,DMEMF_XDATA);
            mem_y_mp3_quantab=Delf_AllocMem(EXTY_MP3_QUANTAB,DMEMF_YDATA);
            prg_mp3=Delf_AddPrg(&DSP56K_MP3);
            if(!prg_mp3 || !mem_x_mp3_ro || !mem_y_mp3_quantab)
            {
                printf("**not enough Delfina memory\n");
                return(0);
            }
            Delf_CopyMem(pow43tab, (void*)(prg_mp3->ydata+DATY_MP3_POW43TAB),
                         8206*4, DCPF_FROM_AMY|DCPF_YDATA|DCPF_32BIT);
            Delf_CopyMem(quantab,  (void*)mem_y_mp3_quantab,
                         512*4,  DCPF_FROM_AMY|DCPF_YDATA|DCPF_32BIT);
            Delf_Run(prg_mp3->prog+PROG_MP3_INIT, 0, 0,
                     mem_il_mp3, mem_ip_mp3,
                     (ULONG)curr_play->freq,
                     (ULONG)mem_x_mp3_ro);
            if((!mem_il_mp3 && !nofastl) || (!mem_ip_mp3 && !nofastp))
            {
                printf("**warning: not enough internal DSP memory\n"
                       "  (the decoder might run too slowly and produce \"jerky\" sound)\n");
            }
            Delf_Poke(prg_mp3->xdata+DATX_MP3_QUANTAB_P, DMEMF_XDATA, mem_y_mp3_quantab);
            Delf_Poke(prg_mp3->ydata+DATY_MP3_FORCEMONO, DMEMF_YDATA, forcemono);
            break;
        default:
            printf("**layer %d is not supported\n",layer);
            return(0);
    }
    delfint.is_Code=(void(*)(void))IntServer;
    if (!(key=Delf_AddIntServer(prg_pcm->prog+PROG_PCM_INTKEY,&delfint)))
    {
        printf("**couldn't create interrupt server\n");
        return(0);
    }
    if(!(mod_pcm=Delf_AddModule(DM_Inputs, 0,
                                DM_Outputs, 1,
                                DM_Code, prg_pcm->prog+PROG_PCM_MODULE,
                                DM_Freq, dacrate?dacrate*1000:freq,
                                DM_Name, "DelfMPEG", 0)))
    {
        printf("**couldn't create DelfModule\n");
        return(0);
    }
    return(1);
}







/*
**
** free Delfina resources **
**
*/
void CleanupDelfina(void)
{
    Delay(10);
    if(mod_pcm) {Delf_RemModule(mod_pcm); mod_pcm=NULL;}
    if(key)     {Delf_RemIntServer(key); key=0;}
    if(prg_mp2) {Delf_RemPrg(prg_mp2); prg_mp2=NULL;}
    if(prg_mp3) {Delf_RemPrg(prg_mp3); prg_mp3=NULL;}
    if(prg_pcm) {Delf_RemPrg(prg_pcm); prg_pcm=NULL;}
    if(mem_il_mp2) {Delf_FreeMem(mem_il_mp2,DMEMF_LDATA|DMEMF_INTERNAL); mem_il_mp2=NULL;}
    if(mem_ip_mp2) {Delf_FreeMem(mem_ip_mp2,DMEMF_PROG |DMEMF_INTERNAL); mem_ip_mp2=NULL;}
    if(mem_il_mp3) {Delf_FreeMem(mem_il_mp3,DMEMF_LDATA|DMEMF_INTERNAL); mem_il_mp3=NULL;}
    if(mem_ip_mp3) {Delf_FreeMem(mem_ip_mp3,DMEMF_PROG |DMEMF_INTERNAL); mem_ip_mp3=NULL;}
    if(mem_x_mp3_ro) {Delf_FreeMem(mem_x_mp3_ro,DMEMF_XDATA); mem_x_mp3_ro=NULL;}
    if(mem_y_mp3_quantab) {Delf_FreeMem(mem_y_mp3_quantab,DMEMF_YDATA); mem_y_mp3_quantab=NULL;}
}






UWORD GetBits(UWORD num)
{
    UWORD val=0;
    for(; num>0; num--)
    {
        if(gb_num==0)
        {
            gb_buf= *gb_pt++;
            gb_num= 16;
        }
        gb_num--;
        val+= val;
        val|= (gb_buf>>gb_num)&1;
    }
    return(val);
}






UWORD CheckHeader(struct loadbuf *lb)
{
    UWORD result=0;

    /** let's ignore these header infos (we don't need them here)
    *** ->version, ->extension, ->copyright, ->original, ->emphasis **/
    lb->header  = currheader;
    lb->layer   = 4-((currheader>>17)&0x3);
    lb->mode    = ((currheader>>6)&0x3);
    lb->modext  = ((currheader>>4)&0x3);
    lb->br_ind  = ((currheader>>12)&0xf);
    lb->freq    = ((currheader>>10)&0x3);
    lb->errprot = ((currheader>>16)&0x1)^0x1;

    if( (lb->layer==4)  ||
        (lb->br_ind==0) ||
        (lb->br_ind==15)||
        (lb->freq==3)   )   result+=1;
    if(prevheader)
        if( (currheader&HDR_CONSTANT)!=(prevheader&HDR_CONSTANT) ||
            ((lb->mode==MPG_MD_MONO?1:2)!=channels) ) result+=2;
    if((currheader&HDR_MPEG1)!=HDR_MPEG1) result+=4;
    return(result);
}






/*
**
** analyze frame header and load frame **
**
*/
int ReadFrame(struct loadbuf *lb)
{
    LONG s0,s1,i0,i1;
    UWORD md;

    if((md=CheckHeader(lb)))
    {
        if(!prevheader && strict)
        {
            printf("**not recognized as MPEG1 audio\n");
            return(0);
        }

        if((currheader&0xffffff00)==ID3V1) printf(" (ID3v1 tag)\n");
        else if(prevheader)
        {
            if(md&4)      printf(" (lost frame sync - non-audio data)\n");
            else if(md&1) printf(" (invalid frame header)\n");
            else if(md&2) printf(" (unsupported frame header change)\n");
            if(verbose)
                printf("**position=0x%08lx  currheader=0x%08lx  "
                   "prevheader=0x%08lx\n",bytes_loaded,currheader,prevheader);
        }

        if(!strict)
        {
            s0=0;
            while((s0<32*1024) && md)
            {
                bytes_loaded++; s0++;
                currheader<<=8;
                i0=ReadCharAsync(file);
                if(i0<0) break;
                else
                {
                    currheader|=i0;
                    md=CheckHeader(lb);
                }
            }
            printf("**skipped %d bytes - resync %s.\n",s0,md?"failed":"ok");
        }

        if(md) return(0);   /* resync failed */
    }


    if(channels==0) channels=(lb->mode==MPG_MD_MONO)?1:2;
    lb->II_translate= mpg_translate [lb->freq]
                                    [(lb->mode==MPG_MD_MONO)?1:0]
                                    [lb->br_ind];
    lb->II_jsbound= (lb->mode==MPG_MD_JOINT_STEREO) ?
                            (lb->modext<<2)+4 : mpg_sblimit[lb->II_translate];

    lb->framesize = (mpg_bitrate[lb->layer-1][lb->br_ind]*144000)
                    /mpg_freq[lb->freq] + ((currheader>>9)&0x1); /* padding */
    lb->delfcopysize = lb->framesize-4;

    s0=i0=2;
    if(lb->errprot)
    {
        i0=ReadAsync(file,&lb->crc,s0);
        lb->delfcopysize-=s0;
    }
    s1=i1=lb->delfcopysize;

    /** do not read layer I frame because it might be too large !! **/
    if(lb->layer>1) i1=ReadAsync(file,&lb->data[0],s1);


    /*** at beginning of file: try to recognize the second frame, too ***/
    if(!prevheader)
    {
        /** seek over first layer I frame (it has not been read) **/
        if(lb->layer==1) i1=SeekAsync(file,s1,MODE_CURRENT);
        if(PeekAsync(file,&currheader,4)==4)
        {
            ULONG ch=currheader;
            prevheader=currheader;
            if(CheckHeader(lb->next))
            {
                printf("** !!! not recognized as MPEG1 audio\n");
                return(0);
            }
            prevheader=0;
            currheader=ch;
        }
    }


    if(!((i0==s0) && (i1==s1)))
    {
        if((i0==-1) || (i1==-1))
            printf(" (file read error)\n");
        else
            if(verbose) printf(" (unexpected EOF - got %d of %d bytes)\n",i1,s1);
        return(0);
    }

    if(lb->layer==3)
    {
        gb_pt= (UWORD*)&lb->data[2]; gb_num=0;
        if(lb->mode==MPG_MD_MONO)
        {
            GetBits(9+5+4-16);
            md=GetBits(12);
        }
        else
        {
            GetBits(9+3+8-16);
            md=GetBits(12);
            GetBits(59-12);
            md+=GetBits(12);
            GetBits(59-12);
            md+=GetBits(12);
        }
        GetBits(59-12);
        md+=GetBits(12);
        lb->III_main_data_size=(md+7)>>3;

        if(lb->mode!=MPG_MD_JOINT_STEREO) lb->modext=0;

        if(buffers_filled==0) BitReservoir(lb);
    }

    buffers_filled++;
    frames_loaded++;
    bytes_loaded+=lb->framesize;

    return(1);
}






void setPosition(ULONG seconds)     /*** WARNING: quick & dirty hack !! ***/
{
    int loop,maxl;
    if(strict) return;

    pause++;
    if(seconds>rexxduration) seconds=rexxduration;
  /*printf("----setPosition %d\n",seconds);*/

    if(xing_flags&XING_FLAG_TOC)
    {
        /*interpolate in TOC to get file seek point in bytes*/
        int a;
        float fa, fb, fx;
        fx=100.0*(float)seconds/(float)rexxduration;
        a=(int)fx;  if(a>99) a=99;
        fa=xing_toc[a];  
        if(a<99) fb=xing_toc[a+1]; else fb=256.0;
        fx=(fa+(fb-fa)*(fx-a))/256.0;
        bytes_loaded=(int)(fx*bytes_total); 
        frames_loaded=frames_played=seconds*freq/1152;
      /*printf("----Xing VBR new pos: %ld of %ld bytes\n",bytes_loaded,bytes_total);*/
    }
    else
    {
        bytes_loaded=setpos_start+(seconds*(bytes_total-setpos_start))/rexxduration;
        frames_loaded=frames_played=bytes_loaded/setpos_framesize;
      /*printf("----plain new pos: %ld of %ld bytes\n",bytes_loaded,bytes_total);*/
    }

    curr_load=curr_play=framebuf0;
    buffers_filled=0;
    bitres_offset=0;

    SeekAsync(file,bytes_loaded,MODE_START);
    maxl=((framebuf>8) ? 8 : framebuf);
    for(loop=0; loop<maxl; loop++)
    {
        ReadAsync(file,&currheader,4);
        if(ReadFrame(curr_load))
        {
            prevheader=currheader;
            curr_load=curr_load->next;
        }
        else {bytes_loaded=bytes_total; break;}
    }
    pause--;
}





    


int main(void)
{
    struct RDArgs *rdargs;
    LONG args[15]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, r1, i, taskpri;
    UBYTE **files_pt, *filename;
    double duration, duration2;
    ULONG sigs, minutes, seconds, millisec, curr_print_pos, prev_print_pos;
    struct timeval time1,time2;
    struct rtFileRequester *rtfilereq=NULL;
    struct rtFileList *rtfilelist=NULL, *rtfilelist_curr=NULL;
    BPTR lock_newdir=NULL, lock_olddir=NULL;

    /*
    **
    ** read args **
    **
    */
    rdargs=ReadArgs(template,args,NULL);
    printf("\33[1m%s by Smack/Infect!\33[0m\n",&version[6]);
    files_pt=(char**)args[0];
    verbose=args[1];
    noplay=args[2];
    showtag=args[3];
    nofastl=args[4];
    nofastp=args[5];
    if(args[6]) framebuf=(*(LONG*)args[6]); else framebuf=DEFAULT_FRAMEBUF;
    if(args[7]) asyncbuf=(*(LONG*)args[7]); else asyncbuf=DEFAULT_ASYNCBUF;
    if(args[8]) ffskip=(*(LONG*)args[8]); else ffskip=DEFAULT_FFSKIP;
    notimer=args[9];
    forcemono=args[10];
    strict=args[11];
    if(args[12]) dacrate=(*(LONG*)args[12]); else dacrate=0;
    if(args[13]) volume=(*(LONG*)args[13]); else volume=100;
    rexxmode=args[14];

    if(verbose)
        printf(
        "\n"
        "  DelfMPEG - MPEG audio player for Delfina DSP\n"
        "  Copyright (C) 1999-2001  Michael Henke\n"
        "\n"
        "  This program is free software; you can redistribute it and/or modify\n"
        "  it under the terms of the GNU General Public License as published by\n"
        "  the Free Software Foundation; either version 2 of the License, or\n"
        "  (at your option) any later version.\n"
        "\n"
        "  This program is distributed in the hope that it will be useful,\n"
        "  but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
        "  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
        "  GNU General Public License for more details.\n"
        "\n"
        "  You should have received a copy of the GNU General Public License\n"
        "  along with this program; if not, write to the Free Software\n"
        "  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n"
        "\n"  );


    /*
    **
    ** open libraries & stuff **
    **
    */
    if(!noplay)
    {
        ULONG delfina_model;
        double delfina_dspclock;
        if (!(DelfinaBase=OpenLibrary("delfina.library",4)))
        {
            printf("**unable to open delfina.library V4\n");
            rc=20;
            goto exit_clean;
        }
        delfina_model=Delf_GetAttr(DA_HWInfo,0);
        delfina_model&=0xff; i=0;
        while(delfina_model) { delfina_model>>=1; i++; }
        delfina_model=i ? (i-1) : 7;
        if((DelfinaBase->lib_Version==4) && (DelfinaBase->lib_Revision<12))
            delfina_dspclock=0.0;
        else
            delfina_dspclock=(double)Delf_GetAttr(DA_DSPClock,0)/1000000.0;
        if(verbose)
            printf("\n  Delfina %s: delfina.library v%d.%d, %d K memory, %.1f MHz DSP\n",
                   delfina_name[delfina_model],
                   DelfinaBase->lib_Version,
                   DelfinaBase->lib_Revision,
                   (Delf_AvailMem(DMEMF_PROG|DMEMF_TOTAL)+Delf_AvailMem(DMEMF_YDATA|DMEMF_TOTAL)+1023)>>10,
                   delfina_dspclock );
    }
    if (!(AsyncIOBase=OpenLibrary("asyncio.library",39))) {
        printf("**unable to open asyncio.library V39\n");
        rc=20;
        goto exit_clean;
    }
    TimerBase=(struct Library*)FindName(&SysBase->DeviceList,"timer.device");
    mytask=FindTask(NULL);
    if(!(fib=AllocDosObject(DOS_FIB,&tag_done)))
    {
        rc=20;
        goto exit_clean;
    }
    if(rexxmode)
    {
        char *res;
        if((res=initRexx()))
        {
            printf("**initRexx failed (%s)\n",res);
            rc=20;
            goto exit_clean;
        }
    }
        

    /*
    **
    ** init load buffers **
    **
    */
    if(framebuf<4) framebuf=4;
    if(asyncbuf<4) asyncbuf=4;
    if(ffskip<1) ffskip=1;
    if(dacrate<0) dacrate=0;
    if(dacrate>48) dacrate=48;
    if(dacrate) dacrate=Delf_GetAttr(DA_Freq,dacrate*1000)/1000;
    if(volume<0) volume=0;
    if(volume>200) volume=200;
    framebuf0size=framebuf*(LONG)sizeof(struct loadbuf);
    if(verbose)
        printf( "\n   reader: framebuf=%d (%d K), asyncbuf=%d K, ffskip=%d, strict=o%s\n"
                "  decoder: forcemono=o%s, dacrate=%d kHz, volume=%d%%\n",
                framebuf, (framebuf0size+1023)>>10, asyncbuf, ffskip, strict?"n":"ff",
                forcemono?"n":"ff", dacrate, volume );
    if(!(framebuf0=(struct loadbuf*)AllocMem(framebuf0size,MEMF_PUBLIC)))
    {
        printf("**not enough memory for \"framebuf\"\n");
        rc=20;
        goto exit_clean;
    }
    for(i=0;i<framebuf-1;i++) framebuf0[i].next= &framebuf0[i+1];
    framebuf0[framebuf-1].next=framebuf0;


    /*
    **
    ** reqtools filerequester if no files specified **
    **
    */
    if(!files_pt && !rexxmode)
    {
        if((ReqToolsBase=(struct ReqToolsBase*)OpenLibrary("reqtools.library",38)))
        {
            if(!(rtfilereq=rtAllocRequest(RT_FILEREQ,NULL)))
            {
                rc=10;
                goto exit_clean;
            }
        }
        else
        {
            rc=5;
            goto exit_clean;
        }
    }

    rexxduration=rexxposition=0;
    pause=1; rexxerror=0;   /* initialize */

next_filereq:
    if(rtfilereq)
    {
        rtfilelist=rtFileRequest( rtfilereq, &rtfilename[0],
                                  "DelfMPEG: select MPEG audio files",
                                  RTFI_Flags, FREQF_MULTISELECT|FREQF_PATGAD,
                                  TAG_DONE );
        if(rtfilelist)
        {
            rtfilelist_curr=rtfilelist;
            if(rtfilereq->Dir)
            {
                lock_newdir=Lock(rtfilereq->Dir,SHARED_LOCK);
                lock_olddir=CurrentDir(lock_newdir);
            }
        }
        else goto exit_clean;
    }

next_rexxloop:
    if(rexxmode)
    {
        ende=1; rexxstatus=0;   /* status: stop */
        rexxfilename[0]=NULL;
        printf("\n  ARexx mode: waiting for 'PLAY <file>' on port '%s'\n",portname);
        while(!rexxfilename[0])
        {
            handleRexx();
            if(CheckSignal(SIGBREAKF_CTRL_D)) goto exit_clean;
            if(ende>1) goto exit_clean;     /* QUIT command */
            Delay(1);
        }
        files_pt=rexxfilename;
        rexxduration=rexxposition=0;
        rexxfiletypebuf[0]=0;
        rexxerror=0;                /* PLAY command clears rexxerror */
        rexxstatus=1;               /* status: play */
        if(pause>1) rexxstatus=2;   /* status: pause */
    }
    ende=0;

    /*
    **
    ** files loop **
    **
    */
    do
    {
        /*
        **
        ** get next filename **
        **
        */
        if(rtfilelist)
        {
            if(rtfilelist_curr)
            {
                filename=rtfilelist_curr->Name;
                rtfilelist_curr=rtfilelist_curr->Next;
            }
            else filename=NULL;
        }
        else filename=(*files_pt++);
        if(!filename) break;
        printf("\n  file: %s\n",filename);

        havetag=0;
        setpos_start=prev_print_pos=curr_print_pos=0;
        curr_load=curr_play=framebuf0;
        bytes_loaded=buffers_filled=buffers_missed=0;
        prevheader=currheader=channels=0;
        frames_loaded=frames_played=0;
        decoder_busy=maindata_err=0;
        bitres_offset=0;

        /*
        **
        ** open file **
        **
        */
        rexxerror=20;  /* file not found */
        if((file=OpenAsync(filename,MODE_READ,asyncbuf<<10)))
        {
            /*
            **
            ** read first frame + output some info **
            **
            */
            ExamineFH(file->af_File,fib);
            bytes_total=fib->fib_Size;
            ReadAsync(file,&currheader,4);

            if(currheader==0x52494646)          /* RIFF WAVE header */
            {
                UBYTE b[8];
                ReadAsync(file,&b[0],8);
                r1=20;  /* 12+8 bytes */
                while(1)
                {
                    if(ReadAsync(file,&b[0],8)!=8)   /* chunk name + length */
                        break;  /* EOF -> ReadFrame() detects this */
                    r1=((LONG)b[7]<<24)|((LONG)b[6]<<16)|((LONG)b[5]<<8)|(LONG)b[4];
                    if((b[0]=='d')&&(b[1]=='a')&&(b[2]=='t')&&(b[3]=='a'))
                        break;                       /* found data chunk */
                    SeekAsync(file,r1,MODE_CURRENT); /* skip unknown chunk */
                }
                bytes_total=r1;
                if(verbose)
                    printf("  found RIFF header (data chunk: %ld bytes)\n",r1);
                ReadAsync(file,&currheader,4);
            }

            if((currheader&0xFFFFFF00)==ID3V2)  /* found ID3V2 tag */
            {
                UBYTE b[6];
                ReadAsync(file,&b[0],6);
                r1=(LONG)b[5]|((LONG)b[4]<<7)|((LONG)b[3]<<14)|((LONG)b[2]<<21);
                SeekAsync(file,r1,MODE_CURRENT);
                r1+=10;
                if(verbose)
                    printf("  skipped ID3v2.%d.%d tag (%d bytes)\n",
                            currheader&0xFF, b[0], r1);
                bytes_loaded+=r1;
                ReadAsync(file,&currheader,4);
            }

            rexxerror=30;  /* file not recognized as MPEG1 audio */
            if(ReadFrame(curr_load))    /* recognize the MPEG audio file */
            {
                if(showtag)
                {
                    LONG oldpos;
                    oldpos=SeekAsync(file, -128, MODE_END);
                    ID3v1_TAG=0; havetag=0;
                    ReadAsync(file, &ID3v1_TAG, 3);
                    if((ID3v1_TAG&0xffffff00)==ID3V1)
                    {
                        ReadAsync(file,&ID3v1_buffer[00],30); /* title  */
                        ReadAsync(file,&ID3v1_buffer[31],30); /* artist */
                        ReadAsync(file,&ID3v1_buffer[62],30); /* album  */
                        ReadAsync(file,&ID3v1_buffer[93],04); /* year   */
                        ReadAsync(file,&ID3v1_buffer[98],30); /* comment*/
                        ReadAsync(file,&ID3v1_buffer[141],1); /* genre  */
                        for(i=0;i<140;i++)
                        {
                            if(ID3v1_buffer[i]<0x20) ID3v1_buffer[i]=0x20;
                        }
                        ID3v1_buffer[30]=0x00;
                        ID3v1_buffer[30+1+30]=0x00;
                        ID3v1_buffer[30+1+30+1+30]=0x00;
                        ID3v1_buffer[30+1+30+1+30+1+4]=0x00;
                        ID3v1_buffer[30+1+30+1+30+1+4+1+30]=0x00;
                        havetag=1;
                    }
                    SeekAsync(file, oldpos, MODE_START);
                }

                {   /* look for Xing VBR header */
                    UBYTE *xpt;
                    xingvbr=xing_flags=0;
                    xpt= &curr_load->data[0];
                    xpt+=(curr_load->mode==MPG_MD_MONO) ? 17 : 32;
                    if((xpt[0]=='X') &&
                       (xpt[1]=='i') &&
                       (xpt[2]=='n') &&
                       (xpt[3]=='g') )
                    {
                        xingvbr++;
                        xpt+=4;
                        xing_flags= (ULONG)xpt[0]<<24 | (ULONG)xpt[1]<<16 |
                                    (ULONG)xpt[2]<<8  | (ULONG)xpt[3];
                        xpt+=4;
                        if(xing_flags&XING_FLAG_FRAMES)
                        {
                            xing_frames=(ULONG)xpt[0]<<24 | (ULONG)xpt[1]<<16 |
                                        (ULONG)xpt[2]<<8  | (ULONG)xpt[3];
                            xpt+=4;
                        }
                        else xing_frames=0;
                        if(xing_flags&XING_FLAG_BYTES) xpt+=4;
                        if(xing_flags&XING_FLAG_TOC)
                        {
                            for(i=0;i<100;i++) xing_toc[i]=xpt[i];
                        }
                        if(verbose) printf("  Xing VBR header found (info: %d frames, TOC=%s)\n",xing_frames,((xing_flags&XING_FLAG_TOC)?"yes":"no"));

                    }
                }

                freq=mpg_freq[curr_load->freq];
                mono=(curr_load->mode==MPG_MD_MONO) ? 1 : 0;
                layer=curr_load->layer;
                setpos_framesize=curr_load->framesize;

                duration=(double)bytes_total/((double)freq/1152.0)/(double)curr_load->framesize;
                r1=mpg_bitrate[layer-1][curr_load->br_ind];
                if(xingvbr && xing_frames)
                {
                    duration=(double)xing_frames*1152.0/(double)freq;
                    r1=(LONG)((double)bytes_total/duration/125.0);
                }
                sprintf(rexxfiletypebuf,
                        "layer %s  %s%03d kbps  %ld Hz  %s",
                        mpg_layername[layer-1],
                        (xingvbr && xing_frames) ? "VBR avg. " : "",
                        r1,
                        freq,
                        mpg_modename[curr_load->mode] );
                printf("  type: %s\n",rexxfiletypebuf);
                rexxduration=(ULONG)(duration+0.5);
                millisec=(ULONG)(1000.0*modf(duration,&duration2));
                minutes=(ULONG)(duration2/60.0);
                seconds=(ULONG)(duration2-minutes*60.0);
                printf("  time: %02ld min %02ld.%03ld sec\n",minutes,seconds,millisec);
                if(verbose) printf("  filesize/framesize: %ld/%ld bytes\n",bytes_total,curr_load->framesize);

                if(showtag && havetag)
                {
                    printf( "  TAG ID3v1\n      title: %s\n     artist: %s\n"
                            "      album: %s\n    comment: %s\n"
                            "       year: %s    genre: %d\n",
                            &ID3v1_buffer[00], /* title  */
                            &ID3v1_buffer[31], /* artist */
                            &ID3v1_buffer[62], /* album  */
                            &ID3v1_buffer[98], /* comment*/
                            &ID3v1_buffer[93], /* year   */
                            (int)ID3v1_buffer[141] ); /* genre  */
                }

                prevheader=currheader;
                curr_load=curr_load->next;

                rexxerror=10;  /* initDelfina failed */
                if(InitDelfina())
                {
                    rexxerror=0;    /* ok */
                    printf("  playing..."); fflush(stdout);
                    taskpri=SetTaskPri(mytask,9);
                    GetSysTime(&time1);
                    if(pause>0) pause--;
                    /*
                    **
                    ** frames loop **
                    **
                    */
                    while(!ende)
                    {
                        if(rexxmode) handleRexx();
                        sigs=CheckSignal(SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D|SIGBREAKF_CTRL_E|SIGBREAKF_CTRL_F);
                        if(sigs&SIGBREAKF_CTRL_C)
                        {
                            printf(" break CTRL-C\n");
                            break;  /* exit frames loop */
                        }
                        if(sigs&SIGBREAKF_CTRL_D)
                        {
                            ende=2;
                            printf(" break CTRL-D\n");
                            break;  /* exit frames loop */
                        }
                        if(sigs&SIGBREAKF_CTRL_E)
                        {
                            if(pause)
                            {
                                pause=0;
                                rexxstatus=1;   /* play */
                                printf("\n  playing...");
                            }
                            else
                            {
                                pause=1;
                                rexxstatus=2;   /* pause */
                                printf(" pause");
                            }
                            fflush(stdout);
                        }
                        if(sigs&SIGBREAKF_CTRL_F)
                        {
                            pause++;
                            i=ffskip;
                            while((buffers_filled>0) && (i>0))
                            {
                                curr_play=curr_play->next;
                                buffers_filled--; i--;
                                frames_played++;
                                if((layer==3) && (buffers_filled>0))
                                    BitReservoir(curr_play);
                            }
                            pause--;
                        }
                        if( (buffers_filled<framebuf) &&
                            (bytes_loaded<bytes_total) )
                        {
                            if(ReadAsync(file,&currheader,4)==4)
                                if(ReadFrame(curr_load))
                                {
                                    prevheader=currheader;
                                    curr_load=curr_load->next;
                                }
                                else bytes_loaded=bytes_total; /* force EOF */
                            else bytes_loaded=bytes_total;
                        }
                        else
                        {
                            if( (bytes_loaded>=bytes_total) &&
                                (buffers_filled==0) )
                            {
                                printf(" done.\n");
                                break;  /* exit frames loop */
                            }

                            if(!notimer)    /* print timer */
                            {
                                curr_print_pos=(frames_played*1152)/freq;
                                if(prev_print_pos!=curr_print_pos)
                                {
                                    rexxposition=curr_print_pos;
                                    prev_print_pos=curr_print_pos;
                                    printf("\r  playing... (%02d:%02d)",
                                        curr_print_pos/60, curr_print_pos%60);
                                    fflush(stdout);
                                }
                            }
                            Delay(1);
                        }
                    }
                    pause++; /* mute output */
                    GetSysTime(&time2);
                    SetTaskPri(mytask,taskpri);
                    SubTime(&time2,&time1);
                    millisec=(ULONG)((double)time2.tv_micro/1000.0);
                    minutes=(ULONG)((double)time2.tv_secs/60.0);
                    seconds=(ULONG)((double)time2.tv_secs-minutes*60.0);
                    duration2=(double)time2.tv_secs+(double)time2.tv_micro/1000000.0;
                    if(verbose)
                    {
                        if(buffers_missed>5) /* prevent "unnecessary" warnings */
                            printf("**note: detected NO_INPUT_DATA %d times\n",buffers_missed);
                        if(decoder_busy>0)
                            printf("**note: detected DECODER_BUSY %d times (%d%%)\n",decoder_busy,decoder_busy*100/frames_played);
                        if(maindata_err>0)
                            printf("**note: detected MP3_MAINDATA_ERROR %d times\n",maindata_err);
                        printf("  frames played/loaded: %ld/%ld\n",frames_played,frames_loaded);
                        printf("  elapsed time: %02ld min %02ld.%03ld sec\n",minutes, seconds, millisec);
                    }
                }
                CleanupDelfina();
            }
            CloseAsync(file);
        }
        else printf("**unable to open file\n");
    } while(!ende);

    if(rtfilelist)
    {
        if(lock_olddir) { CurrentDir(lock_olddir); lock_olddir=NULL; }
        if(lock_newdir) { UnLock(lock_newdir); lock_newdir=NULL; }
        rtFreeFileList(rtfilelist); rtfilelist=NULL;
        if(ende<2) goto next_filereq;
    }
    if(rexxmode && (ende<2)) goto next_rexxloop;

exit_clean:
    cleanupRexx();
    if(rdargs) FreeArgs(rdargs);
    if(framebuf0) FreeMem((APTR)framebuf0,framebuf0size);
    if(fib) FreeDosObject(DOS_FIB,fib);
    if(rtfilereq) rtFreeRequest(rtfilereq);
    if(ReqToolsBase) CloseLibrary((struct Library*)ReqToolsBase);
    if(AsyncIOBase) CloseLibrary(AsyncIOBase);
    if(DelfinaBase) CloseLibrary(DelfinaBase);
    return(rc);
}
