/* playhandler: sowas wie /dev/audio fuer sparks
   Mountlist:

   Handler     = L:PlayHandler
   Priority    = 5
   Stacksize   = 1000
   GlobVec     = -1
*/

#ifdef LATTICE
#define __USE_SYSBASE
#endif

#include <dos/dos.h>
#include <dos/dosextens.h>
#include <dos/filehandler.h>
#include <devices/audio.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <graphics/gfxbase.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/alib_protos.h>
#include <stdlib.h>
#include <ctype.h>

#define BUFLEN 512
#define BUFFERS 20
#define SAMPLES 8192

#define MIN(A,B) (((A)<(B))?(A):(B))

struct VHDR {
ULONG oneShotHiSamples,repeatHiSamples,samplesPerHiCycle;
UWORD samplesPerSec;
UBYTE ctOctave,sCompression;
LONG volume;
};

enum parserstates {init,vhdr,readover,body,no8svx,identifyhunk};

struct streaminfo {
UBYTE *buf[2];
ULONG buflen;
struct IOAudio req[5];
char which;
char deflt;
char played[2];
ULONG cnt;
UWORD samples;
char channel;
UBYTE parsebuf[sizeof(struct VHDR)];
UBYTE bufcnt;
ULONG chunkcnt;
enum parserstates parserstate;
};

struct DosPacket *getpacket(struct MsgPort *);
void replypacket(struct DosPacket *,LONG,LONG);
LONG playhandler(void);
void playpacket(struct streaminfo *,APTR,LONG);
void parsepacket(struct DosPacket *);
int openstream(struct DosPacket *);
#ifdef LATTICE
void geta4(void);
#endif
char *BSTR2C(char *);
void parsename(struct streaminfo *,LONG);

extern struct ExecBase *
#ifdef LATTICE
                         __far
#endif
                                AbsExecBase;

struct ExecBase *SysBase;
struct GfxBase *GfxBase;
struct DosLibrary *DOSBase;
LONG dmaclock;
LONG buffers;

static char version[]="$VER: PLAY-Handler V1.00 by JD - "
#ifdef SHARE
"Shareware Version"
#else
"Full Version licenced to "
"Joerg Dorchain"
#endif
;

/* saveds ??*/

LONG playhandler()
{
struct MsgPort *pp;
struct DosPacket *dp;
struct Process *me;
struct streaminfo *sip;
struct DeviceNode *dn,**pdn,*dn2;
struct DosInfo *di;
int die;
int open;

#ifdef LATTICE
geta4();
#endif

SysBase=AbsExecBase;

me=(struct Process *)SysBase->ThisTask;
if (me->pr_CLI!=NULL)
  return 5;
pp=&me->pr_MsgPort;

WaitPort(pp);
dp=getpacket(pp);
dn=((struct DeviceNode *)BADDR(dp->dp_Arg3));
dn->dn_Task=pp;
replypacket(dp,DOSTRUE,0);

die=0;
open=0;
buffers=BUFFERS;
dmaclock=3579545;
if ((GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",0))!=NULL)
 {
  if ((GfxBase->DisplayFlags&PAL)==NULL)
    dmaclock=357945;
  CloseLibrary((struct Library *)GfxBase);
 }

for(;;)
 {
  WaitPort(pp);
  if ((dp=getpacket(pp))!=NULL)
   {
    switch(dp->dp_Type)
     {
      case ACTION_FINDUPDATE:
      case ACTION_FINDINPUT:
      case ACTION_FINDOUTPUT:
        if ((die==1)
#ifdef SHARE
                   ||(open==1)
#endif
                              )
          replypacket(dp,DOSFALSE,ERROR_OBJECT_IN_USE);
        else
          if(openstream(dp)==1)
           {
            open++;
#ifdef SHARE
            die=1;
#endif
           }
        break;
      case ACTION_END:
        sip=(struct streaminfo *)dp->dp_Arg1;
        if (sip->cnt!=0)
         {
          sip->req[sip->which].ioa_Request.io_Command=
          sip->req[sip->which+2].ioa_Request.io_Command=CMD_WRITE;
          sip->req[sip->which].ioa_Request.io_Flags=
          sip->req[sip->which+2].ioa_Request.io_Flags=ADIOF_PERVOL;
          sip->req[sip->which].ioa_Data=
          sip->req[sip->which+2].ioa_Data=sip->buf[sip->which];
          sip->req[sip->which].ioa_Length=
          sip->req[sip->which+2].ioa_Length=sip->cnt;
          sip->req[sip->which].ioa_Period=
          sip->req[sip->which+2].ioa_Period=dmaclock/sip->samples;
          sip->req[sip->which].ioa_Volume=
          sip->req[sip->which+2].ioa_Volume=64;
          sip->req[sip->which].ioa_Cycles=
          sip->req[sip->which+2].ioa_Cycles=1;
          if ((sip->channel==1)&&((sip->played[0]|sip->played[1])==0))
           {
            sip->req[4].ioa_Request.io_Command=CMD_STOP;
            DoIO((struct IORequest *)&sip->req[4]);
           }
          BeginIO((struct IORequest *)&sip->req[sip->which]);
          if (sip->channel==1)
           {
            BeginIO((struct IORequest *)&sip->req[sip->which+2]);
            if ((sip->played[0]|sip->played[1])==0)
             {
              sip->req[4].ioa_Request.io_Command=CMD_START;
              DoIO((struct IORequest *)&sip->req[4]);
             }
           }
          WaitIO((struct IORequest *)&sip->req[sip->which]);
          if (sip->channel==1)
            WaitIO((struct IORequest *)&sip->req[sip->which+2]);
         }
        sip->req[4].ioa_Request.io_Command=CMD_RESET;
        DoIO((struct IORequest *)&sip->req[4]);
        CloseDevice((struct IORequest *)&sip->req[4]);
        DeletePort(sip->req[0].ioa_Request.io_Message.mn_ReplyPort);
        FreeMem(sip->buf[1],sip->buflen);
        FreeMem(sip->buf[0],sip->buflen);
        FreeMem(sip,sizeof(struct streaminfo));
        open--;
        replypacket(dp,DOSTRUE,0);
        break;
      case ACTION_READ:
      case ACTION_SEEK:
      case ACTION_SET_FILE_SIZE:
        replypacket(dp,-1,ERROR_ACTION_NOT_KNOWN);
        break;
      case ACTION_WRITE:
        parsepacket(dp);
        replypacket(dp,dp->dp_Arg3,0);
        break;
      case ACTION_IS_FILESYSTEM:
        replypacket(dp,0,0);
        break;
      case ACTION_DIE:
        die=1;
        replypacket(dp,DOSTRUE,0);
        break;
      case ACTION_MORE_CACHE:
        buffers+=dp->dp_Arg1;
        if(buffers<1)
          buffers=1;
        replypacket(dp,buffers,buffers);
        break;
      default:
        replypacket(dp,DOSFALSE,ERROR_ACTION_NOT_KNOWN);
        break;
     }
   }
  if ((open==0)&&(die==1))
   {
    if ((DOSBase=(struct DosLibrary *)OpenLibrary("dos.library",0))!=NULL)
     {
      if (DOSBase->dl_lib.lib_Version>36)
        LockDosList(LDF_WRITE|LDF_ALL|LDF_DELETE);
      di=BADDR(((struct RootNode *)DOSBase->dl_Root)->rn_Info);
      pdn=(struct DeviceNode **)&di->di_DevInfo;
      Forbid();
      while((dn2=BADDR(*pdn))!=dn)
        pdn=(struct DeviceNode **)&dn2->dn_Next;
      *pdn=(struct DeviceNode *)dn->dn_Next;
      UnLoadSeg(dn->dn_SegList);
      FreeMem(BADDR(dn->dn_Name),((UBYTE *)BADDR(dn->dn_Name))[0]+1);
      FreeMem(BADDR(dn->dn_Handler),((UBYTE *)BADDR(dn->dn_Handler))[0]+1);
      FreeMem(dn,sizeof(struct DeviceNode));
      if (DOSBase->dl_lib.lib_Version>36)
        UnLockDosList(LDF_WRITE|LDF_ALL|LDF_DELETE);
      while((dp=getpacket(pp))!=NULL)
        replypacket(dp,-1,ERROR_DEVICE_NOT_MOUNTED);
      return 0;
     }
    die=0;
   }
 }
}

struct DosPacket *getpacket(mp)
struct MsgPort *mp;
{
struct Message *mn;

if ((mn=GetMsg(mp))==NULL)
  return NULL;
return (struct DosPacket *)mn->mn_Node.ln_Name;
}

void replypacket(dp,arg1,arg2)
struct DosPacket *dp;
LONG arg1,arg2;
{
struct MsgPort *mp;
struct Message *mn;

mp=dp->dp_Port;
mn=dp->dp_Link;

mn->mn_Node.ln_Name=(char *)dp;
dp->dp_Port=&((struct Process *)SysBase->ThisTask)->pr_MsgPort;
dp->dp_Res1=arg1;
dp->dp_Res2=arg2;

PutMsg(mp,mn);
}

void playpacket(sip,buf,len)
struct streaminfo *sip;
APTR buf;
LONG len;
{
LONG cnt2;

 cnt2=0;
 while(sip->buflen-sip->cnt<=len-cnt2)
  {
   CopyMem(((UBYTE *)buf)+cnt2,
           ((UBYTE *)sip->buf[sip->which])+sip->cnt,
           sip->buflen-sip->cnt);
   sip->req[sip->which].ioa_Request.io_Command=
     sip->req[sip->which+2].ioa_Request.io_Command=CMD_WRITE;
   sip->req[sip->which].ioa_Request.io_Flags=
     sip->req[sip->which+2].ioa_Request.io_Flags=ADIOF_PERVOL;
   sip->req[sip->which].ioa_Data=
     sip->req[sip->which+2].ioa_Data=sip->buf[sip->which];
   sip->req[sip->which].ioa_Length=
     sip->req[sip->which+2].ioa_Length=sip->buflen;
   sip->req[sip->which].ioa_Period=
     sip->req[sip->which+2].ioa_Period=dmaclock/sip->samples;
   sip->req[sip->which].ioa_Volume=
     sip->req[sip->which+2].ioa_Volume=64;
   sip->req[sip->which].ioa_Cycles=
     sip->req[sip->which+2].ioa_Cycles=1;
   if ((sip->channel==1)&&((sip->played[0]|sip->played[1])==0))
    {
     sip->req[4].ioa_Request.io_Command=CMD_STOP;
     DoIO((struct IORequest *)&sip->req[4]);
    }
   BeginIO((struct IORequest *)&sip->req[sip->which]);
   if (sip->channel==1)
    {
     BeginIO((struct IORequest *)&sip->req[sip->which+2]);
     if ((sip->played[0]|sip->played[1])==0)
      {
       sip->req[4].ioa_Request.io_Command=CMD_START;
       DoIO((struct IORequest *)&sip->req[4]);
      }
    }
   sip->played[sip->which]=1;
   cnt2+=sip->buflen-sip->cnt;
   sip->which=1-sip->which;
   sip->cnt=0;
   if (sip->played[sip->which]==1)
    {
     WaitIO((struct IORequest *)&sip->req[sip->which]);
     if (sip->channel==1)
       WaitIO((struct IORequest *)&sip->req[sip->which+2]);
     sip->played[sip->which]=0;
    }
  }
 CopyMem(((UBYTE *)buf)+cnt2,
         ((UBYTE *)sip->buf[sip->which])+sip->cnt,
         len-cnt2);
 sip->cnt+=len-cnt2;
}

void parsepacket(dp)
struct DosPacket *dp;
{
struct streaminfo *sip;
LONG cnt2;
LONG min;
ULONG ident;

sip=(struct streaminfo *)dp->dp_Arg1;
cnt2=0;
while(dp->dp_Arg3>cnt2)
 {
  switch (sip->parserstate)
   {
    case no8svx:
      playpacket(sip,((UBYTE *)dp->dp_Arg2)+cnt2,dp->dp_Arg3-cnt2);
      cnt2=dp->dp_Arg3;
      break;
    case init:
      min=MIN(12-sip->bufcnt,dp->dp_Arg3-cnt2);
      CopyMem(((UBYTE *)dp->dp_Arg2)+cnt2,
              sip->parsebuf+sip->bufcnt,
              min);
      cnt2+=min;
      sip->bufcnt+=min;
      if (sip->bufcnt==12)
       {
         if((*(ULONG *)sip->parsebuf==0x464f524d)&&
            (*(ULONG *)(sip->parsebuf+8)==0x38535658))
           sip->parserstate=identifyhunk;
         else
          {
           sip->parserstate=no8svx;
           playpacket(sip,sip->parsebuf,12);
          }
         sip->bufcnt=0;
        }
       break;
     default: /*identify,vhdr,body,readover*/
       if ((sip->parserstate==identifyhunk)||(sip->chunkcnt==0))
        {
         min=MIN(4-sip->bufcnt,dp->dp_Arg3-cnt2);
         CopyMem(((UBYTE *)dp->dp_Arg2)+cnt2,
              sip->parsebuf+sip->bufcnt,
              min);
         cnt2+=min;
         sip->bufcnt+=min;
         if (sip->bufcnt==4)
          {
           sip->bufcnt=0;
           ident=*(ULONG *)sip->parsebuf;
           if (sip->parserstate==identifyhunk)
            {
             switch (ident)
              {
               case 0x464f524d: /*FORM*/
                 sip->parserstate=init;
                 sip->bufcnt=4;
                 break;
               case 0x56484452: /*VHDR*/
                 sip->parserstate=vhdr;
                 break;
               case 0x424f4459: /*BODY*/
                 sip->parserstate=body;
                 break;
               default:
                 sip->parserstate= readover;
                 break;
              }
            }
           else
             sip->chunkcnt=ident+ident%2;
          }
        }
       else
        {
         min=MIN(sip->chunkcnt,dp->dp_Arg3-cnt2);
         switch(sip->parserstate)
          {
           case vhdr:
             CopyMem(((UBYTE *)dp->dp_Arg2)+cnt2,
                     sip->parsebuf+sip->bufcnt,
                     min);
             sip->bufcnt+=min;
             if (sip->bufcnt==sizeof(struct VHDR))
              {
               if (sip->deflt==1)
                 sip->samples=((struct VHDR *)sip->parsebuf)->samplesPerSec;
               sip->bufcnt=0;
              }
             break;
           case body:
             playpacket(sip,((UBYTE *)dp->dp_Arg2)+cnt2,min);
             break;
          }
         sip->chunkcnt-=min;
         cnt2+=min;
         if (sip->chunkcnt==0)
           sip->parserstate=identifyhunk;
        }
       break;
   }
 }
}

int openstream(dp)
struct DosPacket *dp;
{
struct streaminfo *sip;
struct MsgPort *mp;
struct FileHandle *fh;
static UBYTE channel0[]={2,4,1,8};
static UBYTE channel1[]={3,5,10,12};
static UBYTE *channels[4]= {channel0,channel1,channel0,channel0+2};
/*  egal, stereo, left, right */
union {
ULONG channel;
APTR ptr;
} unit;

if ((sip=AllocMem(sizeof(struct streaminfo),MEMF_ANY|MEMF_CLEAR))!=NULL)
{
  sip->buflen=buffers*BUFLEN;
  if ((sip->buf[0]=AllocMem(sip->buflen,MEMF_CHIP))!=NULL)
   {
    if ((sip->buf[1]=AllocMem(sip->buflen,MEMF_CHIP))!=NULL)
     {
      if ((mp=CreatePort("",0))!=NULL)
       {
        parsename(sip,dp->dp_Arg3);
        sip->req[0].ioa_Request.io_Message.mn_Node.ln_Type=NT_MESSAGE;
        sip->req[0].ioa_Request.io_Message.mn_Length=sizeof(struct IOAudio);
        sip->req[0].ioa_Request.io_Message.mn_ReplyPort=mp;
        sip->req[0].ioa_Request.io_Command=ADCMD_ALLOCATE;
        sip->req[0].ioa_Request.io_Flags=ADIOF_NOWAIT;
        sip->req[0].ioa_Data=channels[sip->channel];
        sip->req[0].ioa_Length=(sip->channel<2)?4:2;
        sip->req[0].ioa_Request.io_Message.mn_Node.ln_Pri=75;
        Forbid();
        if (OpenDevice("audio.device",0,(struct IORequest *)&sip->req[0],0)==NULL)
         {
          sip->req[0].ioa_Request.io_Message.mn_Node.ln_Pri=127;
          sip->req[0].ioa_Request.io_Command=ADCMD_SETPREC;
          DoIO((struct IORequest *)&sip->req[0]);
          Permit();
          sip->req[4]=sip->req[1]=sip->req[0];
          if (sip->channel==1)
           {
            sip->req[3]=sip->req[2]=sip->req[0];
            unit.ptr=sip->req[4].ioa_Request.io_Unit;
            unit.channel&=6;
            sip->req[0].ioa_Request.io_Unit=
              sip->req[1].ioa_Request.io_Unit=unit.ptr;
            unit.ptr=sip->req[4].ioa_Request.io_Unit;
            unit.channel&=9;
            sip->req[2].ioa_Request.io_Unit=
              sip->req[3].ioa_Request.io_Unit=unit.ptr;
           }
          sip->parserstate=init;
          fh=(struct FileHandle *)BADDR(dp->dp_Arg1);
          fh->fh_Arg1=(LONG)sip;
          replypacket(dp,DOSTRUE,0);
          return 1;
         }
        Permit();
        DeletePort(mp);
       }
      FreeMem(sip->buf[1],sip->buflen);
     }
    FreeMem(sip->buf[0],sip->buflen);
   }
  FreeMem(sip,sizeof(struct streaminfo));
 }
replypacket(dp,DOSFALSE,ERROR_NO_FREE_STORE);
return 0;
}

char *BSTR2C(bstr)
char *bstr;
{
static char cstr[256];
int i;

bstr=BADDR(bstr);
for(i=0;i<=bstr[0];i++)
 cstr[i]=bstr[i+1];
cstr[i]=0;
return cstr;
}

void parsename(sip,bstr)
struct streaminfo *sip;
LONG bstr;
{
char *fn;

fn=BSTR2C((char *)bstr);
while(*(fn++)!=':');
*fn=tolower(*fn);
if ((*fn=='s')||(*fn=='l')||(*fn=='r'))
 {
  sip->channel=(*fn=='s')?1:(*fn=='l')?2:3;
  fn++;
 }
if ((sip->samples=atol(fn))==0)
 {
  sip->deflt=1;
  sip->samples=SAMPLES;
 }
}

