/*******************************************************************
*
* $VER: PlaySample.c 1.1 (8.6.97) Tak Tang (tst92@ecs.soton.ac.uk)
*
* The PlaySound() module plays an 8svx sound sample which may have
* been packed using xpk.
*
* This file is in the Public Domain
*
********************************************************************
*
*/

/**** Header files ****/

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/execbase.h>

#include <devices/audio.h>
#include <hardware/cia.h>

#include <dos/dos.h>
#include <libraries/xpk.h>

#include <clib/alib_protos.h>
#include <clib/dos_protos.h>
#include <clib/exec_protos.h>

#include <pragmas/dos_pragmas.h>
#include <pragmas/exec_pragmas.h>

#include "xPlay.h"
#include "PlaySample.h"


/* sCompression: Choice of compression algorithm applied to the samples. */
#define sCmpNone        0       /* not compressed */
#define sCmpFibDelta    1       /* Fibonacci-delta encoding (Appendix C) */
#define xCmpExpDelta    2       /* Exponential-delta encoding - unofficial
                                   (see aminet mus/misc/wavepak.lha) */

/* CHUNK IDs */
#define MAKE_ID(a,b,c,d)        \
        ((ULONG) (a)<<24 | (ULONG) (b)<<16 | (ULONG) (c)<<8 | (ULONG) (d))

#define ID_FORM MAKE_ID('F','O','R','M')
#define ID_NAME MAKE_ID('N','A','M','E')
#define ID_AUTH MAKE_ID('A','U','T','H')
#define ID_ANNO MAKE_ID('A','N','N','O')
#define ID_FVER MAKE_ID('F','V','E','R')
#define ID_NULL MAKE_ID(' ',' ',' ',' ')

#define ID_8SVX MAKE_ID('8','S','V','X')
#define ID_VHDR MAKE_ID('V','H','D','R')
#define ID_BODY MAKE_ID('B','O','D','Y')

#define ID_ATAK MAKE_ID('A','T','A','K')
#define ID_RLSE MAKE_ID('R','L','S','E')

/* No. of Sample Buffers */

#define NumBuffers 2


/* ---------- Voice8Header ---------------------------------------------*/

struct Voice8Header
{
    ULONG oneShotHiSamples;     /* # samples in the high octave 1-shot part */
    ULONG repeatHiSamples;      /* # samples in the high octave repeat part */
    ULONG samplesPerHiCycle;    /* # samples/cycle in high octave, else 0 */
    UWORD samplesPerSec;        /* data sampling rate */
    UBYTE ctOctave;             /* # of octaves of waveforms */
    UBYTE sCompression;         /* data compression technique used */
    ULONG volume;               /* playback nominal volume from 0 to 0x10000
                                 * (full volume). Map this value into
                                 * the output hardware's dynamic range.
                                 */
};


/* Static Data */

UBYTE Channels[2][2] = { 1,8, 2,4 };

/* Access to CIA registers */

struct CIA *ciaa=(struct CIA*)0xbfe001;


/****** PlaySample() ***********************************************
*
*   NAME
*       PlaySample() -- Plays an 8svx sample, packed (maybe) using xpk
*
*   SYNOPSIS
*       error = PlaySample ( GlobalData , SampleName, Period,
*               Volume, Priority, Double,   Mono,
*               Quiet,  Filter,   NoFilter, Raw )
*
*       ULONG = PlaySample ( struct GlobalData * , STRPTR, ULONG,
*               ULONG,  ULONG,    ULONG,  ULONG,
*               ULONG,  ULONG,    ULONG,  BOOL);
*
*   FUNCTION
*       Opens a file SampleName using xpk.  If it is an 8SVX form,
*       it will parse the file, picking out the headder, and playing
*       the body.
*
*       It will abort on CTRL-C or CTRL-D.  A CTRL-C will also set
*       the gd->UserStop flag.
*
*   INPUTS
*       GlobalData -- Pointer to struct GlobalData, containing library
*                     bases, and other global data.
*       SampleName -- File name of sound sample, relative to current
*                     directory
*       Period     -- New period.  "0" uses period in VHDR
*       Volume     -- New volume.  "0" uses volume in VHDR
*       Priority   -- Sample priority.
*       Double     --
*       Mono       --
*       Quiet      -- Disable (CLI) progress indicator
*       Filter     -- Turn on hardware filter.
*       NoFilter   -- Turn off hardware filter.
*       Raw        -- Accept non-iff samples, and replay as 8bit unsigned
*
*   RESULT
*       RETURN_OK   if played OK
*       RETURN_FAIL general failure
*       ERROR_ABORT user pressed CTRL-C or CTRL-D
*       (may be others)
*
*   EXAMPLE
*
*   NOTES
*      Most of this code came from xPlay by Christian Buchner.
*
*   BUGS
*       Does not play fibonacci compressed samples.  Just aborts.
*       Does not handle ATAK or RLSE chunks.  Just ignores.
*
*   SEE ALSO
*
********************************************************************
*
* 1.1 (9.6.97) tst
*     Added code to restore filter
*     Added code to report if aborted by CTRL-C or CTRL-D
*     Hacked it out of xPlay.c
*
* 1.0 (?.?.??) cb
*     First version
*
*/


LONG PlaySample(struct GlobalData *gd,
  UBYTE *Sample, ULONG Period, ULONG Volume, ULONG Priority, ULONG Double,
  ULONG Mono, ULONG Quiet, ULONG Filter, ULONG NoFilter, BOOL PC)
{
    LONG ReturnCode=RETURN_FAIL;
    struct XpkFH *xfh=NULL;
    struct TagItem Tag[10];
    UBYTE ErrorMsg[XPKERRMSGSIZE];
    ULONG BufSize;
    UBYTE *Buffer[NumBuffers]={0,0};
    LONG Got[NumBuffers];
    struct MsgPort *ReplyPort[NumBuffers][2][2]={0,0,0,0,0,0,0,0};
    struct IOAudio *AudIO[NumBuffers][2][2]={0,0,0,0,0,0,0,0};
    LONG b,s,c;
    LONG NumSides = (Mono ? 1 : 2);
    LONG NumChannels = (Double ? 2 : 1);
    BOOL Flag[NumBuffers]={FALSE,FALSE};
    BOOL Ende=FALSE;
    ULONG Offset=0;
    ULONG Length;
    ULONG ToPlay;
    ULONG Position=0;
    ULONG Pos[NumBuffers];
    ULONG signals;
    LONG i;
    UBYTE *p;

    Tag[0].ti_Tag=XPK_InName;    Tag[0].ti_Data=(ULONG)Sample;
    Tag[1].ti_Tag=XPK_PassThru;  Tag[1].ti_Data=(ULONG)TRUE;
    Tag[2].ti_Tag=XPK_GetError;  Tag[2].ti_Data=(ULONG)ErrorMsg;
    Tag[3].ti_Tag=TAG_DONE;

    if (XpkOpen(&xfh,Tag))
    {
        Printf("Error opening %s: %s\n",Sample,ErrorMsg);
        goto CleanUp;
    }
    BufSize=xfh->NLen;
    for (b=0;b<NumBuffers;b++)
    {
        if (!(Buffer[b]=AllocVec(BufSize+XPK_MARGIN,MEMF_CHIP)))
        {
            Printf("Not enough chip memory for sample buffer %ld.\n",b+1);
            ReturnCode=ERROR_NO_FREE_STORE;
            goto CleanUp;
        }
    }
    for (b=0;b<NumBuffers;b++)
    {
        for (s=0;s<NumSides;s++)
        {
            for (c=0;c<NumChannels;c++)
            {
                if(!(ReplyPort[b][s][c] = CreateMsgPort())) goto CleanUp;
                if(!(AudIO[b][s][c] = (struct IOAudio *)CreateIORequest(ReplyPort[b][s][c],sizeof(struct IOAudio)))) goto CleanUp;
                AudIO[b][s][c]->ioa_Request.io_Message.mn_Node.ln_Pri=Priority ? Priority : 0;
                if (!b && !s && !c)
                {
                    if (OpenDevice(AUDIONAME ,0L,(struct IORequest *)AudIO[b][s][c],0L))
                    {
                        Printf("Error opening audio.device!\n");
                        goto CleanUp;
                    }
                }
                else
                {
                    AudIO[b][s][c]->ioa_Request.io_Device = AudIO[0][0][0]->ioa_Request.io_Device;
                }
                if (b==0)
                {
                    AudIO[b][s][c]->ioa_Data = Channels[s];
                    AudIO[b][s][c]->ioa_Length = 4/NumSides;
                    AudIO[b][s][c]->ioa_Request.io_Flags = ADIOF_NOWAIT;
                    AudIO[b][s][c]->ioa_Request.io_Command = ADCMD_ALLOCATE;
                    BeginIO((struct IORequest *)AudIO[b][s][c]);
                    if (WaitIO((struct IORequest *)AudIO[b][s][c]))
                    {
                        Printf("Can't get audio channels.\n");
                        goto CleanUp;
                    }
                }
                else
                {
                    AudIO[b][s][c]->ioa_AllocKey = AudIO[0][s][c]->ioa_AllocKey;
                    AudIO[b][s][c]->ioa_Request.io_Unit = AudIO[0][s][c]->ioa_Request.io_Unit;
                }
            }
        }
    }

    Length=ToPlay=xfh->ULen;

    if (!Quiet) Printf("\r\33[KPlaying %s\r",Sample);

    if (Filter)
    {
        ciaa->ciapra &= ~(CIAF_LED);
    }
    if (NoFilter)
    {
        ciaa->ciapra |= CIAF_LED;
    }

    for (b=0 ; Flag[b] || (!Ende) ; b=(b+1)%NumBuffers)
    {
        if (Flag[b])
        {
            ULONG Percentage;
            for (s=0;s<NumSides;s++)
            {
                for (c=0;c<NumChannels;c++)
                {
                    signals= Wait( (1L<<ReplyPort[b][s][c]->mp_SigBit)
                                   | SIGBREAKF_CTRL_C
                                   | SIGBREAKF_CTRL_D);
                    if ( signals & (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D) )
                    {
                        ReturnCode=ERROR_BREAK;
                        if (signals & SIGBREAKF_CTRL_C) gd->UserStop=TRUE;
                        if (!Quiet) Printf("\rPlaying %s - ABORTED \n",Sample);
                        goto CleanUp;
                    }
                    if (WaitIO((struct IORequest*)AudIO[b][s][c]))
                    {
                        Printf("\nChannels have been stolen.\n");
                        ReturnCode=RETURN_ERROR;
                        goto CleanUp;
                    }
                }
            }
            if ((Percentage=100*Pos[b]/Length)<100)
            {
                if (!Quiet) Printf("\rPlaying %s (%2ld%%)\r",Sample,Percentage);
            }
        }

        if ((Got[b]=XpkRead(xfh, Buffer[b], BufSize))<0)
        {
            Printf("\nError reading from %s: %s\n",Sample,ErrorMsg);
            goto CleanUp;
        }

        if (!Position)
        {
            if (*(ULONG*)(Buffer[b]) == ID_FORM)
            {
                if (*(ULONG*)(Buffer[b]+8) == ID_8SVX)
                {
                    UBYTE *Chunk=Buffer[b]+12;

                    for (;;)
                    {
                        ULONG ChunkLen=((*(ULONG*)(Chunk+4))+1) & 0xFFFFFFFE;

                        if (*(ULONG*)(Chunk) == ID_VHDR)
                        {
                            struct Voice8Header *VHDR=(struct Voice8Header*)(Chunk+8);

                            if (!Volume)
                            {
                                Volume=VHDR->volume*64/0x10000;
                            }
                            if (!Period)
                            {
                                Period=(*(struct ExecBase**)(4))->ex_EClockFrequency*5/VHDR->samplesPerSec;
                            }
                            if (VHDR->sCompression != sCmpNone)
                            {
                                Printf("\nError! Can't play a compressed sample.\n");
                                goto CleanUp;
                            }
                        }

                        if (*(ULONG*)(Chunk) == ID_BODY)
                        {
                            Length=ToPlay=ChunkLen;
                            Offset=(ULONG)Chunk+8-(ULONG)Buffer[b];
                            break;
                        }

                        Chunk+=ChunkLen+8;
                    }
                }
            }
        }

        if (PC) for (i=0,p=Buffer[b];i<Got[b];i++) (*p++) += 0x80;

        Pos[b]=(Position+=(Got[b]-=Offset));

        if (Got[b] && ToPlay)
        {
            ULONG PlayLength=(ToPlay >= Got[b] ? Got[b] : ToPlay);

            for (s=0;s<NumSides;s++)
            {
                for (c=0;c<NumChannels;c++)
                {
                    AudIO[b][s][c]->ioa_Data = Buffer[b]+Offset;
                    AudIO[b][s][c]->ioa_Length = PlayLength;
                    AudIO[b][s][c]->ioa_Period = Period ? Period : 200;
                    AudIO[b][s][c]->ioa_Volume = Volume ? Volume : 64;
                    AudIO[b][s][c]->ioa_Cycles = 1;
                    AudIO[b][s][c]->ioa_Request.io_Flags = ADIOF_PERVOL|ADIOF_NOWAIT;
                    AudIO[b][s][c]->ioa_Request.io_Command = CMD_WRITE;
                    SetSignal(0L,1L<<ReplyPort[b][s][c]->mp_SigBit);
                }
            }
            Forbid();
            for (s=0;s<NumSides;s++)
            {
                for (c=0;c<NumChannels;c++)
                {
                    BeginIO((struct IORequest*)AudIO[b][s][c]);
                }
            }
            Permit();

            Offset=0;
            ToPlay-=PlayLength;
            Flag[b]=TRUE;
        }
        else
        {
            Flag[b]=FALSE;
            Ende=TRUE;
        }
    }
    if (!Quiet) Printf("\rPlayed  %s - OK \n",Sample);
    ReturnCode=RETURN_OK;

CleanUp:
    for (b=0;b<NumBuffers;b++)
    {
        for (s=0;s<NumSides;s++)
        {
            for (c=0;c<NumChannels;c++)
            {
                if (AudIO[b][s][c])
                {
                    if (AudIO[b][s][c]->ioa_Request.io_Device)
                    {
                        if (b==0)
                        {
                            AbortIO((struct IORequest*)AudIO[b][s][c]);
                            WaitIO((struct IORequest*)AudIO[b][s][c]);
                            AudIO[b][s][c]->ioa_Request.io_Command=ADCMD_FREE;
                            DoIO((struct IORequest*)AudIO[b][s][c]);
                        }
                        if (b==NumBuffers-1 && s==NumSides-1 && c==NumChannels-1)
                        {
                            CloseDevice((struct IORequest*)AudIO[b][s][c]);
                        }
                    }
                    DeleteIORequest(AudIO[b][s][c]);
                }
                if (ReplyPort[b][s][c]) DeleteMsgPort(ReplyPort[b][s][c]);
            }
        }
    }

    for (b=0;b<NumBuffers;b++)
    {
        if (Buffer[b]) FreeVec(Buffer[b]);
    }


    if (Filter)
    {
      ciaa->ciapra |= CIAF_LED;
    }
    if (NoFilter)
    {
      ciaa->ciapra &= ~(CIAF_LED);
    }

    if (xfh) XpkClose(xfh);

    return(ReturnCode);
} /* PlaySample() */

/**** End of file ****/

