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

    AudioCDXL.c

    W.D.L 930330

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

/*
 * COPYRIGHT: Unless otherwise noted, all files are Copyright (c) 1993-1999
 * Amiga, Inc.  All rights reserved.
 *
 * DISCLAIMER: This software is provided "as is".  No representations or
 * warranties are made with respect to the accuracy, reliability, performance,
 * currentness, or operation of this software, and all use is at your own risk.
 * Neither Amiga nor the authors assume any responsibility or liability
 * whatsoever with respect to your use of this software.
 */


// Tab size is 8!

#include <exec/types.h>
#include <graphics/gfxbase.h>

#include <devices/audio.h>
#include "devices/cd.h"

#include <hardware/custom.h>
#include <hardware/dmabits.h>
#include <hardware/intbits.h>

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

#include <pragmas/exec_pragmas.h>

#include <string.h>	// for setmem()

#include "cdxl/cdxlob.h"
#include "cdxl/runcdxl.h"
#include "cdxl/debugsoff.h"

/*	// Uncomment to get debug output turned on
#define KPRINTF
#include "cdxl/debugson.h"
*/


IMPORT struct ExecBase	* SysBase;
IMPORT struct Custom far custom;

STATIC UBYTE		* Name0 = "CDXLAUD0";
STATIC struct Interrupt	* Aud0PriorInterrupt;
STATIC struct Interrupt	  AUD0Interrupt;
STATIC BOOL		  Aud0PriorEnable;
STATIC BOOL		  Aud1PriorEnable;
STATIC BOOL		  Aud0PriorSet;
STATIC BOOL		  Aud1PriorSet;
STATIC BOOL		  aud0_installed;


STATIC struct MsgPort	* AudioPort[4];
STATIC struct IOAudio	  IOAudio[4];
STATIC UBYTE		  Channels[] = {1,2,4,8};
STATIC BOOL		  DeviceOpen[4];

STATIC UWORD	Period;		// Global Period

IMPORT	CDXLOB * CDXL_OB;	// Global CDXLOB
IMPORT	LONG	XLSignal;
IMPORT	struct Process * parent;


IMPORT	struct GfxBase	* GfxBase;


IMPORT	ULONG	Count;
ULONG		SaveCount,AudioCount;
WORD		padjust;

IMPORT BOOL	AudioSignalTask;

/*
 * The AUD0 interrupt code. This routine gets called immediately after
 * the audio DMA channel has read the location and length registers and
 * stored their values in the back up registers. The philospy here is to
 * rewrite the location registers to point to the other audio buffer in
 * preparation for the next time the audio DMA reads them. It seems that
 * the audio period that was specified is sometimes off by less than 1.
 * For long CDXL files, this will result in the audio getting out of
 * sync with the video. What I do here is add or subtract one from the
 * period to compensate for this. A better way to do this could probably
 * be figured out.
 */
VOID __interrupt __saveds
AUD0Handler( VOID )
{
    // clear AUD0 interrupt
    custom.intreq = INTF_AUD0;

    ++AudioCount;

    if( !(CDXL_OB->flags & CDXL_DOSXL) ) {

	padjust = AudioCount - Count;
	custom.aud[ 0 ].ac_per = custom.aud[ 1 ].ac_per = Period + padjust;

	if ( (SaveCount == Count) ) {
	    CDXL_OB->curAudio ^= 1;
	    AudioCount--;
	} else if ( (Count - SaveCount) > 1) {
	    AudioCount++;
	    CDXL_OB->curAudio = CDXL_OB->curVideo;
	} else {
	    CDXL_OB->curAudio = CDXL_OB->curVideo;
	}

	D(PRINTF("%ld-%ld ",CDXL_OB->curAudio,padjust);)

	// Point the location registers to the buffer that is currently being filled
	// by the CDXL. If we are timed right, when the CDXL is done reading, the
	// audio DMA should be ready to reread these location registers.
	custom.aud[ 0 ].ac_ptr = custom.aud[ 1 ].ac_ptr = (USHORT *)CDXL_OB->audio[CDXL_OB->curAudio];

	if ( AudioSignalTask )
	    Signal( (struct Task *)parent, XLSignal );

    } else {
	// Point the location registers to the buffer that is currently being filled
	// by the CDXL. If we are timed right, when the CDXL is done reading, the
	// audio DMA should be ready to reread these location registers.
	padjust = (AudioCount - Count);
	custom.aud[ 0 ].ac_per = custom.aud[ 1 ].ac_per = Period + padjust;
	custom.aud[ 0 ].ac_ptr = custom.aud[ 1 ].ac_ptr = (USHORT *)CDXL_OB->audio[CDXL_OB->curAudio];


	if ( (SaveCount == Count) ) {
	    CDXL_OB->curAudio ^= 1;
	    AudioCount--;
	} else if ( (Count - SaveCount) > 1) {
	    AudioCount++;
	    CDXL_OB->curAudio = CDXL_OB->curVideo;
	} else {
	    CDXL_OB->curAudio = CDXL_OB->curVideo;
	}

//	custom.aud[ 0 ].ac_ptr = custom.aud[ 1 ].ac_ptr = (USHORT *)CDXL_OB->audio[CDXL_OB->curAudio];

	Signal( (struct Task *)parent, XLSignal );
    }

    SaveCount = Count;

} // AUD0Handler()


/*
 * Free the audio channels.
 */
STATIC VOID
FreeAudio( VOID )
{
    int i;

    for ( i = 0; i < 4; i++ ) {
	if ( DeviceOpen[i] ) {
	    CloseDevice( (struct IORequest *)&IOAudio[i] );
	    DeviceOpen[i] = NULL;
	    D(PRINTF("Closing channel %ld\n",i);)
	} else {
	    D(PRINTF("NOT Closing channel %ld\n",i);)
	}

	if ( AudioPort[i] ) {
	    DeleteMsgPort( AudioPort[i] );
	    AudioPort[i] = NULL;
	    D(PRINTF("Deleting Port %ld\n",i);)
	} else {
	    D(PRINTF("NOT Deleting Port %ld\n",i);)
	}
    }

} // FreeAudio()


/*
 * Allocate the audio channels. Probably don't need to allocate all 4 but...
 */
STATIC
AllocAudio( VOID )
{
    int	i,ret = RC_OK;

    for ( i = 0; i < 4; i++ ) {
	setmem( &IOAudio[i], sizeof ( struct IOAudio ), 0 );

	if ( AudioPort[i] = CreateMsgPort() ) {
	    IOAudio[i].ioa_Request.io_Message.mn_ReplyPort = AudioPort[i];
	    IOAudio[i].ioa_Request.io_Message.mn_Node.ln_Pri = 127;
	    IOAudio[i].ioa_AllocKey = 0;
	    IOAudio[i].ioa_Data = &Channels[i];
	    IOAudio[i].ioa_Length = 1;
	    if ( !OpenDevice("audio.device",0,(struct IORequest *)&IOAudio[i],0) ) {
		DeviceOpen[i] = TRUE;
		D(PRINTF("Got channel %ld\n",i);)
	    } else {
		DeviceOpen[i] = FALSE;
		D(PRINTF("Did NOT get channel %ld\n",i);)
		ret = RC_NO_AUDIODEVICE;
		break;
	    }
	} else {
	    ret = RC_NO_MEM;
	    break;
	}
    }

    if ( ret )
	FreeAudio();

    return( ret );

} // AllocAudio()


/*
 * Disable AUD0 & AUD1 DMA as well as the AUD0 interrupt.
 */
VOID
StopAudio( VOID )
{
    // disable AUD0 DMA
    custom.dmacon = DMAF_AUD0;

    // disable AUD1 DMA
    custom.dmacon = DMAF_AUD1;

    // disable AUD0 interrupt
    custom.intena = INTF_AUD0;

    // clear AUD0 interrupt
    custom.intreq = INTF_AUD0;

} // StopAudio()


/*
 * Enable AUD0 & AUD1 DMA as well as the AUD0 interrupt.
 */
VOID
StartAudio( VOID )
{
    // clear AUD0 interrupt
    custom.intreq = INTF_AUD0;

    // enable AUD0 DMA
    custom.dmacon = DMAF_SETCLR|DMAF_MASTER|DMAF_AUD0;

    // enable AUD1 DMA
    custom.dmacon = DMAF_SETCLR|DMAF_MASTER|DMAF_AUD1;

    // enable AUD0 interrupt
    custom.intena = INTF_SETCLR|INTF_AUD0;

    AudioCount = 0;

} // StartAudio()


/*
 * Restore the audio system back to how we found it.
 */
VOID
QuitAudio( VOID )
{
    if ( aud0_installed ) {
	aud0_installed = FALSE;

	StopAudio();

	/*
	 * Remove the interrupt we installed and replace it
	 * with the old one (if there was one).
	 */
	SetIntVector(INTB_AUD0, Aud0PriorInterrupt);

	if(Aud0PriorEnable) {
	    custom.intena = INTF_SETCLR|INTF_AUD0;
	    Aud0PriorEnable = FALSE;
	}

	if(Aud1PriorEnable) {
	    custom.intena = INTF_SETCLR|INTF_AUD1;
	    Aud1PriorEnable = FALSE;
	}

	if ( Aud0PriorSet ) {
	    custom.intreq = INTF_AUD0;
	    Aud0PriorSet = FALSE;
	}

	if ( Aud1PriorSet ) {
	    custom.intreq = INTF_AUD1;
	    Aud1PriorSet = FALSE;
	}
    }

    // Free the audio channels we allocated.
    FreeAudio();

} // QuitAudio()


/*
 * Install an AUD0 interrupt
 */
AddAudioInterrupt( VOID )
{
    setmem( &AUD0Interrupt, sizeof ( AUD0Interrupt ), 0 );

    /* Initialize the Interrupt node */
    AUD0Interrupt.is_Node.ln_Type = NT_INTERRUPT;
    AUD0Interrupt.is_Node.ln_Pri  = 0;
    AUD0Interrupt.is_Node.ln_Name = Name0;

    AUD0Interrupt.is_Data = (APTR)NULL;
    AUD0Interrupt.is_Code = AUD0Handler;

    custom.intena = INTF_AUD0|INTF_AUD1;
    custom.intreq = INTF_AUD0|INTF_AUD1;

    // Install the new interrupt handler
    Aud0PriorInterrupt = SetIntVector( INTB_AUD0, &AUD0Interrupt );

    if (Aud0PriorInterrupt) {
	D(PRINTF("Replaced the %ls AUD0 interrupt handler\n",
	    Aud0PriorInterrupt->is_Node.ln_Name);)
    }

    return( (int)(aud0_installed = TRUE) );

} // AddAudioInterrupt()


/*
 * Set up the audio system.
 */
InitAudio( CDXLOB * CDXL_ob )
{
    ULONG	freq;
    LONG	rate;
    UWORD	period;
    int		ret;

    // If this CDXL has no audio, there is no need.
    if ( !CDXL_ob->AudioSize )
	return( RC_OK );

    if ( ret = AllocAudio() )
	return( ret );

    // Save state of AUD0 & AUD1 interrupts
    Aud0PriorEnable = custom.intenar & INTF_AUD0 ? TRUE : FALSE;
    Aud1PriorEnable = custom.intenar & INTF_AUD1 ? TRUE : FALSE;
    Aud0PriorSet = custom.intreqr & INTF_AUD0 ? TRUE : FALSE;
    Aud1PriorSet = custom.intreqr & INTF_AUD1 ? TRUE : FALSE;

    // Add the Audio interrupt
    AddAudioInterrupt();

    // Calculate period
    freq = ( SYSTEM_PAL ? PAL_FREQ : NTSC_FREQ );

    D(
     if ( SYSTEM_PAL ) {
	D(PRINTF("SYSTEM_PAL... \n");)
     } else {
	D(PRINTF("SYSTEM_NTSC... \n");)
     }
    )

    D(PRINTF("InitAudio() 4.1 freq= %ld, CDXL_ob->AudioSize= %ld, CDXL_ob->FrameSize= %ld\n",
	freq,CDXL_ob->AudioSize,CDXL_ob->FrameSize);)

    if ( CDXL_ob->flags & CDXL_DOSXL) {
	rate = INTDIV( (( CDXL_ob->ReadXLSpeed * DEFAULT_SECTOR_SIZE) * CDXL_ob->AudioSize ), CDXL_ob->FrameSize );
    } else {
	rate = INTDIV( ( DATA_TRANS_RATE * CDXL_ob->AudioSize ), CDXL_ob->FrameSize );
    }

    period = (UWORD) INTDIV( freq, rate );

    D(PRINTF("InitAudio() 4.2 rate= %ld, period= %ld\n",rate,period);)

    Period = period;	// Set Global pointer
    padjust = 0;	// Global period adjuster.

    CDXL_ob->curAudio = 0;

    // Set up the volume,period and length
    custom.aud[ 0 ].ac_vol = custom.aud[ 1 ].ac_vol = CDXL_ob->Volume;
    custom.aud[ 0 ].ac_per = custom.aud[ 1 ].ac_per = period;
    custom.aud[ 0 ].ac_ptr = custom.aud[ 1 ].ac_ptr = (USHORT *)CDXL_ob->audio[CDXL_ob->curAudio];
    custom.aud[ 0 ].ac_len = custom.aud[ 1 ].ac_len = ( CDXL_ob->AudioSize / sizeof( UWORD) );

    return( RC_OK );

} // InitAudio()
