/***************************************************************************
*	NAME:  RECORD.C $Revision: 1.9 $
**	COPYRIGHT:
**	"Copyright (c) 1994,1995 by e-Tek Labs"
**
**       "This software is furnished under a license and may be used,
**       copied, or disclosed only in accordance with the terms of such
**       license and with the inclusion of the above copyright notice.
**       This software or any other copies thereof may not be provided or
**       otherwise made available to any other person. No title to and
**       ownership of the software is hereby transfered."
****************************************************************************
* $Log: record.c $
* Revision 1.9  1995/11/17 08:15:20  sdsmith
* Switched to 16 ints per second and double buffering DMA.
* 
* Revision 1.8  1995/11/10 15:02:01  sdsmith
* Changed for Win95 compatability
* Revision 1.7  1995/10/30 08:51:59  sdsmith
* Added 60mS resolution for Win95 compatability tests
* Revision 1.6  1995/10/27 16:19:56  sdsmith
* Changes for Wave compat tests in win95
* Revision 1.5  1995/05/26 16:08:57  teckert
* Fixed Kids Studio bug
* Revision 1.4  1995/04/26 16:22:24  sdsmith
* Fixed the public function commentary
* Revision 1.3  1995/03/08 13:45:22  sdsmith
* Added codec record position function
* Revision 1.2  1995/02/24 13:53:08  mleibow
* Revision 1.1  1995/02/23 11:07:54  unknown
* Initial revision
***************************************************************************/
#include <stdlib.h>
#include <dos.h>

#include "iw.h"
#include "iwl.h"
#include "globals.h"
#include "digital.h"
#include "codec.h"
#include "dma.h"

#define EXTRA_SAMPLES 100
//#define DMA_SPLIT 2

#ifdef _WINDOWS
void far * _pascal MemCopySrc(void far *lpDst, void far *lpSrc, unsigned int cnt);
void far * _pascal MemCopyDst(void far *lpDst, void far *lpSrc, unsigned int cnt);
#define SEGINC 8u
// #define DEBUG
#ifdef DEBUG
void _pascal OutputDebugString(char far *);
int column = 0;
extern char buffer[];
char *pointer = buffer;
#endif
#endif

extern int iwl_set_synth_record_dma();
extern void iwl_dig_set_freq(USHORT, ULONG);
extern struct dig_voice_status dig_voice_status[32];
extern volatile struct iwl_dma_parms iwl_dma_parms[NUM_CHANNELS];
extern struct dig_voice_status codec_record_voice;

static void check_sample_db(int);
static int record_codec_handler(void);
static unsigned long last_rec_position,in_handler;
static unsigned int DMA_SPLIT;

/***************************************************************************/
/* CODEC recording section                                                 */
/***************************************************************************/

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

FUNCTION DEFINTION:
record_init - initialize record facilities

DESCRIPTION:
record_init initializes the record tracking voice number and adds a voice
interrupt handler to receive control from the record tracking voice.

RETURNS: int - IW_OK
*/
int iwl_record_init(void)
{
    record_voice = -1;
  //iw_add_voice_handler(record_voice_handler);
    return(IW_OK);
}

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

FUNCTION DEFINITION:
iwl_codec_setup_record_dma - setup DMA controller and CODEC for sampling

DESCRIPTION:
iwl_codec_setup_record_dma checks if the virtual DMA channel to be used for
sampling is free.  If so, this routine initializes the virtual DMA channel
data structure, programs the DMA controller, and programs the CODEC play
count.

NOTE: the virtual DMA channel is usually IW_DMA_CHAN_1 for CODEC sampling

RETURNS: int - IW_OK if operation was successful
               IW_DMA_BUSY if the virtual DMA channel is busy
*/
static int iwl_codec_setup_record_dma(
  int rec_dma_chan, /* virtual DMA channel for recording */
  struct dig_voice_status RFAR *vs) /* voice data structure */
{
    struct iw_dma_buff RFAR *dma;

    OS_PUSH_DISABLE();
    if (!iw_dma_ready(rec_dma_chan)) {
	OS_POP_FLAGS();
	return(IW_DMA_BUSY);
    }

    dma = vs->pc_stbuff;
    iwl_set_dma_active_state(rec_dma_chan, 1);
    iwl_dma_parms[rec_dma_chan].current_page = DMA_GET_PAGE(dma->paddr);
    iwl_dma_parms[rec_dma_chan].current_addr = DMA_GET_ADDR(dma->paddr);
    iwl_dma_parms[rec_dma_chan].size = vs->b_size;
    iwl_dma_parms[rec_dma_chan].amount_to_xfer = vs->b_size;
    iwl_dma_parms[rec_dma_chan].amount_xferred = 0;
    os_pgm_dma(rec_dma_chan, vs->b_size,
      DMAMODE_SINGLE_MODE|DMAMODE_WRITE|DMAMODE_AUTOINIT,
      DMA_GET_PAGE(dma->paddr),
      DMA_GET_ADDR(dma->paddr));
    iwl_codec_program_record_count((unsigned int)vs->max);
    OS_POP_FLAGS();
    return(IW_OK);
}

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

FUNCTION DEFINITION:
iwl_codec_start_record_dma - starts CODEC DMA transfer for sampling

DESCRIPTION:
iwl_codec_start_record_dma enables the CODEC to begin DMA transfer of 
digital audio data from the ADCs to the DMA buffer.

NOTE: This routine should not be called until the DMA channel is 
      prepared with iwl_codec_setup_record_dma

RETURNS: void
*/
static void iwl_codec_start_record_dma()
{
    OS_PUSH_DISABLE();

  /* kick off codec here ... */
    OS_OUTPORTB(iwl_codec_base,CONFIG_1);
    iwl_cfig1i |= CODEC_RE;
    OS_OUTPORTB(iwl_codec_data,iwl_cfig1i);

    OS_POP_FLAGS();
}

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

FUNCTION DEFINITION:
iwl_codec_stop_record_dma - stop CODEC DMA transfer for sampling

DESCRIPTION:
iwl_codec_stop_record_dma shutsdown the CODEC DMA transfer of 
digital audio data from the ADCs to the DMA buffer.

RETURNS: void
*/
static void iwl_codec_stop_record_dma()
{
    int rec_dma_chan;

    OS_PUSH_DISABLE();

    OS_OUTPORTB(iwl_codec_base,CONFIG_1);
    iwl_cfig1i &= ~CODEC_RE;
    OS_OUTPORTB(iwl_codec_data,iwl_cfig1i);

    rec_dma_chan = IW_DMA_CHAN_1;
    os_stop_dma(rec_dma_chan);
    iwl_set_dma_active_state(rec_dma_chan, 0);

    OS_POP_FLAGS();
}

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

FUNCTION DEFINITION:
iwl_codec_program_record_count - setup of the CODEC record count

DESCRIPTION:
iwl_codec_program_record_count programs the number of bytes the CODEC will
transfer from the ADCs before generating an interrupt.  The sample size
of the data and whether the data is stereo is taken into account when
the count is programmed.

RETURNS: unsigned int - actual record count programmed based on data format

*/
static unsigned int iwl_codec_program_record_count(
  unsigned int size) /* number of bytes to be sampled before interrupt */
{
    unsigned char temp;

    temp = iwl_crdfi & 0xE0;	/* isolate the format bits */
    switch (temp) {
	case 0x40:		/* 16 bit litle endian */
	case 0xC0:		/* 16 bit big endian */
	    size >>= 1;
	    break;
	case 0xA0:		/* 16 bit ADPCM */
	    size >>= 2;
	    break;
    }

    if (iwl_crdfi & CODEC_STEREO && temp != 0xA0) // not if ADPCM
	size >>= 1;

    size--;
    OS_PUSH_DISABLE();
    OS_OUTPORTB(iwl_codec_base,LOWER_RECORD_COUNT);
    OS_OUTPORTB(iwl_codec_data,(char)size);
    OS_OUTPORTB(iwl_codec_base,UPPER_RECORD_COUNT);
    OS_OUTPORTB(iwl_codec_data,(char)(size>>8));
    OS_POP_FLAGS();
    return(size);
}

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

FUNCTION DEFINTION:
iw_codec_restart_record - restart sampling via the CODEC

DESCRIPTION:
iw_codec_restart_record responds to the iw_restart_digital call when the
voice indicated is the CODEC recording voice.  This routine restarts the
data transfer from the CODEC ADCs to the DMA buffer after it was stopped
with an iw_pause_digital call.

PARAMETERS:
	voice - identifies the codec voice being used for recording.
			(this will be returned to the wrapper on the call to
			iw_codec_record_digital).

RETURNS: void
*/
#ifdef __BORLANDC__
#pragma warn -par
#endif
void iw_codec_restart_record(
  int voice) /* voice to restart recording */
{
    struct dig_voice_status
    *vs;

    vs = &codec_record_voice;
    if ((vs->status & STAT_MASK) == STAT_USER_PAUSED) {
	vs->status &= ~STAT_MASK;
	vs->status |= STAT_PLAYING;
	iwl_codec_enable_irqs();
	iwl_codec_start_record_dma();
    }
}
#ifdef __BORLANDC__
#pragma warn .par
#endif

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

FUNCTION DEFINTION:
iw_codec_start_record - start sampling via the CODEC

DESCRIPTION:
iwl_codec_start_record responds to the iw_start_digital call when the
voice indicated is the CODEC recording voice.  This routine restarts the
data transfer from the CODEC ADCs to the DMA buffer.

This routine may only be called after the recording was initiated with
iw_codec_record_digital.

PARAMETERS:
	voice - identifies the codec voice being used for recording.
			(this will be returned to the wrapper on the call to
			iw_codec_record_digital).

RETURNS: void
*/
void iw_codec_start_record(
  int voice) /* voice to start recording */
{
    iw_codec_restart_record(voice);
}

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

FUNCTION DEFINTION:
iw_codec_stop_record - stop recording via the CODEC

DESCRIPTION:
This routine stops the
data transfer from the CODEC ADCs to the DMA buffer.  In addition,
any remaining empty data buffers are returned to the caller with a
IW_DIG_BUFFER_DONE callback.  After all activity is stopped and the
data buffers returned, the IW_DIG_DONE callback is sent.

This routine may be called at any time to shut down all recording
activity on the indicated voice.

PARAMETERS:
	voice - identifies the codec voice being used for recording.
			(this will be returned to the wrapper on the call to
			iw_codec_record_digital).

RETURNS: void
*/
void RFAR iw_codec_stop_record(
  int voice) /* voice to stop recording */
{
    struct dig_voice_status *vs;

    if (voice == IW_CODEC_RECORD_VOICE) {
	vs = &codec_record_voice;
	if (vs->status != STAT_UNUSED) {
	    iwl_codec_stop_record_dma();
	    IWL_CODEC_OUT(STATUS_3, ~CODEC_RFDI);
	    vs->status = STAT_PAUSED;
	    check_sample_db(IW_CODEC_RECORD_VOICE);
	    if (vs->callback) {
		OS_PUSH_DS();
		(*vs->callback)(IW_DIG_DONE, voice, 0L, 0L);
		OS_POP_DS();
	    }
	    iwl_init_codec_voice(vs);
	    last_rec_position = 0;
	    in_handler = 0;
	    vs->position = 0;
	}
    }
}

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

FUNCTION DEFINTION:
iw_codec_pause_record - pause recording via the CODEC

DESCRIPTION:
This routine stops the
data transfer from the CODEC ADCs to the DMA buffer.  However, unlike
iwl_codec_stop_record, no data is returned to the wrapper and the
IW_DIG_DONE callback is not sent.

PARAMETERS:
	voice - identifies the codec voice being used for recording.
			(this will be returned to the wrapper on the call to
			iw_codec_record_digital).

RETURNS: void
*/
#ifdef __BORLANDC__
#pragma warn -par
#endif
void iw_codec_pause_record(
  int voice) /* voice to pause recording */
{
    struct dig_voice_status
    *vs;


    vs = &codec_record_voice;
    OS_OUTPORTB(iwl_codec_base, CONFIG_1);
    iwl_cfig1i &=  ~CODEC_RE;
    OS_OUTPORTB(iwl_codec_data, iwl_cfig1i);

    vs->status &= ~STAT_MASK;
    vs->status |= STAT_USER_PAUSED;
}
#ifdef __BORLANDC__
#pragma warn .par
#endif

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

FUNCTION DEFINITION:
iw_codec_record_digital - initiate digital sampling through the CODEC

DESCRIPTION:
This routine performs the setup of the CODEC to account for the
digital data format and the DMA buffer size.  If the caller has not
requested IW_TYPE_PRELOAD, the sampling is started otherwise this routine
exits and we wait for an iw_codec_start_digital call.

PARAMETERS:
	buffer - pointer to a buffer to receive the data sampled
	size   - size of the above buffer in bytes
	frequency - rate at which to sample the data
	type      - how to sample the data, this is a bitwise OR of one
				or more of the following:
				IW_TYPE_8BIT
				IW_TYPE_PRELOAD
				IW_TYPE_INVERT_MSB
				IW_TYPE_STEREO
				IW_TYPE_ALAW
				IW_TYPE_ULAW
				IW_TYPE_ADPCM
	st_work_buff - buffer to be used for DMA transfer from the
				   CODEC A/D converters
	callback - address of a function provided by the wrapper to
				receive notofications from the kernel during the
				sampling process

NOTE: This routine MUST be the first routine called when an application is
      going to perform digital sampling.

RETURNS: int - voice number used for CODEC record
               IW_NO_MORE_VOICES if the DMA channel for the CODEC is busy
*/
int iw_codec_record_digital(
	UCHAR RFAR *buffer,
	ULONG size,
	USHORT frequency,
	UCHAR type,
	struct iw_dma_buff RFAR *st_work_buff,
	int (RFAR *callback)(int, int, UCHAR RFAR * RFAR *, ULONG RFAR *))
{
    struct dig_voice_status
    *vs;
    int voice;
    unsigned long resolution;

    if (iwl_codec_base &&
	    codec_record_voice.status == STAT_UNUSED &&
	    iw_dma_ready(IW_DMA_CHAN_1)) {
	vs = &codec_record_voice;
	voice = IW_CODEC_RECORD_VOICE;
	vs->status = STAT_PAUSED;
	vs->pc_buffer = buffer;
	vs->pc_stbuff = st_work_buff;
	vs->size = size;
	vs->rate = frequency;
	vs->type = type;
	vs->volume = 0;
	vs->callback = callback;
	vs->extra_voice = voice;
		vs->max = frequency;
		if (type & IW_TYPE_STEREO) vs->max <<= 1;
		if (!(type & IW_TYPE_8BIT)) vs->max <<= 1;
		vs->max = (vs->max >> 4) & (unsigned long)0xFFFC;
		DMA_SPLIT = 2;
		if ((vs->max * DMA_SPLIT) > vs->pc_stbuff->size) {
		    vs->max = vs->pc_stbuff->size/DMA_SPLIT;
		}
	vs->b_size = vs->max * DMA_SPLIT;
	vs->insert_buf = 0;
	vs->play_buf = 0;
	vs->position = 0;
	last_rec_position = 0;
	in_handler = 0;
	vs->buffs[vs->play_buf].addr_s = 0;
	vs->buffs[vs->play_buf].status = 1;

	/* Set up the recording format */
	iwl_codec_data_format(vs, RECORD_FORMAT);

	/* Program the DMA controller */
	iwl_codec_setup_record_dma(IW_DMA_CHAN_1, vs);

	/* Set up the CODEC interrupt function */
	iwl_codec_capture_func = (void (RFAR *))record_codec_handler;

	/* Start the recording */
	vs->status &= ~STAT_MASK;
	vs->status = STAT_USER_PAUSED;
	if (!(vs->type & IW_TYPE_PRELOAD)) {
	    iw_codec_start_record(IW_CODEC_RECORD_VOICE);
	}
    }
    else {
	voice = IW_NO_MORE_VOICES;
    }
    return(voice);
}

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

FUNCTION DEFINITION:
iw_codec_record_position - report number of bytes sampled

DESCRIPTION:
This routine returns the number of bytes that have been transfered from the
CODEC A/D converters to the wrapper since the last iw_codec_start_record
call.

PARAMETERS:
	voice - identifies the codec voice being used for recording.
			(this will be returned to the wrapper on the call to
			iw_codec_record_digital).

RETURNS: ULONG - number of bytes returned
*/
ULONG iw_codec_record_position(int voice)
{
    struct dig_voice_status
    *vs = &codec_record_voice;
    ULONG position,xferred,acct;
    unsigned long buffers;


    if ((vs->status & STAT_MASK) == STAT_PLAYING) {
	do {
	    buffers = in_handler;
	    //position = (in_handler >> 1) * vs->b_size;
	    position = vs->position;
	    xferred = vs->b_size - os_dma_count(IW_DMA_CHAN_1);
	    if (xferred > vs->max) xferred -= vs->max;
	    position += xferred;
	    //if (position < vs->position) position += vs->b_size;
	} while (buffers != in_handler);
    }
    else {
	    position = vs->position;
    }
    if (position < last_rec_position) {
	position = last_rec_position;
    }
    else {
	last_rec_position = position;
    }

    return(position);
}

/*****************************************************************************/
/* Interrupt Handlers                                                        */
/*****************************************************************************/
/***************************************************************************

FUNCTION DEFINTION:
check_sample_db - copy sample data from DMA buffer to data buffer

DESCRIPTION:
check_sample_db copies sample data from DMA buffer to a data buffer provided
by the wrapper or application.  If the data does not fill the data buffer
the data buffer is retained until the next half of the DMA buffer is 
filled.

When the wrapper's data buffer is full, it is returned to the wrapper via
a IW_DIG_BUFFER_DONE call. Whenever a data buffer is returned, this routine
asks the wrapper for another empty buffer to put data in.  This is necessary
in order to always have a place to put sampled data when a DMA buffer
half fills up.

A new empty data buffer is requested with a IW_DIG_MORE_DATA callback.  THe
wrapper can respond three ways to this request:
1. IW_DIG_MORE_DATA - the wrapper has passed in an empty buffer to hold
                      more data
2. IW_DIG_PAUSE - the wrapper has not passed in an empty buffer and the
                  sampling is to be paused.

3. IW_DIG_DONE - the wrapper has no more empty buffers and sampling is to
                 be stopped.

RETURNS: void
*/
#ifdef _MSC_VER
#pragma optimize("",off)
#endif
static void check_sample_db(int voice)
{
    struct dig_voice_status
    *vs = &codec_record_voice;
    int ret_val;
    UCHAR RFAR *dma;
    ULONG xferred, dma_size, transfer_size;

#ifdef _WINDOWS
    void far *newpc;
#endif

    if ((vs->status & STAT_MASK) == STAT_PLAYING)
	dma_size = vs->max;
    else {
	xferred = vs->b_size - os_dma_count(IW_DMA_CHAN_1);
	if (xferred > vs->max) xferred -= vs->max;
	dma_size = xferred;
    }
    dma = &vs->pc_stbuff->vptr[vs->insert_buf * vs->max];

    while (dma_size > 0 && vs->size > 0) {
	transfer_size = min(vs->size, dma_size);

#ifdef _WINDOWS
	newpc = MemCopyDst((void RFAR *)vs->pc_buffer,
		   (void RFAR *)dma,
		   (int)transfer_size);
#else
	iwu_memcpy((void RFAR *)vs->pc_buffer,
			   (void RFAR *)dma,
	   (size_t)transfer_size);
#endif
	vs->size -= transfer_size;
	dma_size -= transfer_size;

	if (dma_size) {
	    dma = &dma[transfer_size];
	}
	else {
	    vs->insert_buf++;
	    if (vs->insert_buf == DMA_SPLIT) {
		vs->insert_buf = 0;
	    }
	}
	vs->position += transfer_size;

	if (vs->size == 0) {
	    if (vs->callback) {
		OS_PUSH_DS();
		(*vs->callback)(IW_DIG_BUFFER_DONE, IW_CODEC_RECORD_VOICE, 0L, 0L);
		OS_POP_DS();
		if ((vs->status & STAT_MASK) == STAT_PLAYING) {
		    OS_PUSH_DS();
		    ret_val = (*vs->callback)(IW_DIG_MORE_DATA, voice,
				      &vs->pc_buffer, &vs->size);
		    OS_POP_DS();

		    if (ret_val == IW_DIG_PAUSE) {
			iw_codec_pause_record(voice);
		    }
		    if (ret_val == IW_DIG_DONE) {
			iw_codec_stop_record(voice);
		    }
		}
	    }
	}
	else {
	    if ((vs->status & STAT_MASK) == STAT_PLAYING) {
#ifdef _WINDOWS
		vs->pc_buffer = (UCHAR RFAR *)newpc;
#else
		vs->pc_buffer = &(vs->pc_buffer[transfer_size]);
#endif
	    }
	    else {
		if (vs->callback) {
		    OS_PUSH_DS();
		    (*vs->callback)(IW_DIG_BUFFER_DONE, IW_CODEC_RECORD_VOICE, 0L, 0L);
		    OS_POP_DS();
		}
	    }
	}
    }
}
#ifdef _MSC_VER
#pragma optimize("",on)
#endif

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

FUNCTION DEFINTION:
record_codec_handler - handle interrupts from the codec record count

DESCRIPTION:
record_codec_handler gains control when the CODEC recording count reaches
zero.  The CODEC recording count is normally set to one half the size of
the DMA buffer.

Once this routine has control, any data in the currently filled half
of the DMA buffer is transferred to a data buffer provided by the 
wrapper.

RETURNS: int - 1 = interrupt was handled
*/
#if defined(__BORLANDC__)
#pragma warn .par
#endif
static int record_codec_handler()
{
    struct dig_voice_status
    *vs = &codec_record_voice;

    in_handler++;
    check_sample_db(IW_CODEC_RECORD_VOICE);
    return(1);
}
