/***************************************************************************
*        NAME:  PLAY.C $Revision: 1.6 $
**       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: play.c $
* Revision 1.6  1995/11/21 17:28:59  sdsmith
* Changes for new Windows init.
* Revision 1.5  1995/10/26 14:59:36  mleibow
* Added mutes for software mixer controls.
* Revision 1.4  1995/10/13 17:24:31  mleibow
* Changed master volume calls to do 10dB drop off instead of 6dB drop off.
* Revision 1.3  1995/05/31 16:52:28  mleibow
* Made synth always use PIO.
* Revision 1.2  1995/04/26 16:22:30  sdsmith
* Fixed the public function commentary
* Revision 1.1  1995/02/23 11:07:50  unknown
* Initial revision
***************************************************************************/
#include <stdlib.h>
#include <dos.h>
#if defined(__BORLANDC__)
#include <mem.h>
#elif defined(_MSC_VER)
#pragma warning (disable : 4704)
#include <memory.h>
#endif
#include "iw.h"
#include "iwl.h"
#include "globals.h"
#include "digital.h"
#include "iwatten.h"

#ifdef IW_MODULE_SYNTH_DIGITAL

#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

static int dig_terminal_count(USHORT);
static int dig_voice_handler(int voice);
static char stereo_dma;
static USHORT last_dma_voice;   /* round-robin dma access */

struct dig_voice_status dig_voice_status[32];
extern int record_voice;

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

FUNCTION DEFINITION:
dig_init - initialize digital audio playback system

DESCRIPTION:
dig_init initializes digital audio playback voice tracking structures and
sets up the voice and DMA interrupt handlers, 

RETURNS: int - IW_OK initialization was successful
               IW_NO_MORE_HANDLERS could not set up interrupt handlers
*/
int iwl_synth_dig_init(void)
{
    unsigned short voice;
    int i;
    struct dig_voice_status *vs;

    stereo_dma=0;
    for (vs=dig_voice_status, voice=0; voice < iwl_voices; voice++, vs++) {
	vs->status = STAT_UNUSED;
	vs->type = 0;
	vs->dma_control = 0;
	vs->pc_buffer = 0L;
	vs->position = 0L;
	vs->size = 0;
	vs->addr_s = 0;
	vs->addr_e = 0;
	vs->filled = 0;
	vs->voice_control = IWL_STOPPED|IWL_STOP;
	vs->volume_control = IWL_STOPPED|IWL_STOP;
	vs->callback = 0L;
	for (i=0; i < MAX_BUFFS; i++) {
	    vs->buffs[i].addr_s = 0;
	    vs->buffs[i].play_size = 0;
	    vs->buffs[i].status = 0;
	}
    }
    last_dma_voice = 0;
    if (iw_add_handler(IW_IRQ_HANDLER_DMA, (int (RFAR *)(void))dig_terminal_count)) {
	return(IW_NO_MORE_HANDLERS);
    }
    if (iw_add_handler(IW_IRQ_HANDLER_VOICE, (int (RFAR *)(void))dig_voice_handler)) {
	return(IW_NO_MORE_HANDLERS);
    }
    return(IW_OK);
}

void iwl_synth_dig_deinit(void)
{
    iw_remove_handler(IW_IRQ_HANDLER_DMA, (int (RFAR *)(void))dig_terminal_count);
    iw_remove_handler(IW_IRQ_HANDLER_VOICE, (int (RFAR *)(void))dig_voice_handler);
}

#ifdef DEBUG
static void draw(int voice)
{
    struct dig_voice_status *vs = &dig_voice_status[voice];
    int buf, next_buf, draw=0, first=1;

    dprintf("    size=%0lx, ", vs->size);
    dprintf("filled=%0lx\n", vs->filled);

    for (buf = vs->play_buf; vs->buffs[buf].status & BUFSTAT_FILLED; buf = next_buf) {
	next_buf = (buf + 1);
	if (next_buf == MAX_BUFFS)
	    next_buf = 0;
	draw=1;
	if (first) {
	    dprintf("    ");
	    first = 0;
	}
	dprintf("buff(%lx+%lx)", vs->buffs[buf].addr_s, vs->buffs[buf].play_size);
	if (vs->buffs[buf].status & BUFSTAT_DONE)
	    dprintf("D");
	if (vs->buffs[buf].status & BUFSTAT_INT)
	    dprintf("I");
	dprintf("->");
	if (next_buf == vs->insert_buf)
	    break;
    }
    if (draw)
	dprintf("\n");
}
#endif

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

FUNCTION DEFINITION:
set_addr_regs - set a pair of address registers for a voice.

DESCRIPTION:
set_addr_regs sets the address registers specified to the address
requested.

The address is first converted to the high and low portions.

The porth and portl parameters identify the address registers to be set.
They should be one of:
  SET_START_HIGH
  SET_START_LOW
  SET_END_HIGH
  SET_END_LOW
  SET_ACC_HIGH
  SET_ACC_LOW

RETURNS: void
*/
static void set_addr_regs(
  int voice,          /* voice to set addresses */
  ULONG addr,         /* address to set */
  int portl,          /* low address register */
  int porth)          /* high address register */
{
    unsigned long offset;
    struct dig_voice_status *vs = &dig_voice_status[voice];

    iwl_set_addr_regs(voice, addr, portl, porth);
    if (vs->type & IW_TYPE_STEREO) {
	if (vs->type & IW_TYPE_8BIT) {
	    offset = vs->st_offset;
	}
	else {
	    offset = vs->st_offset >> 1;
	}
	iwl_set_addr_regs(vs->extra_voice, addr+offset, portl, porth);
    }
}

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

FUNCTION DEFINITION:
set_pan - set the pan position registers for a voice

RETURNS: void
*/
static void set_pan(
  USHORT voice,  /* voice to set pan position */
  USHORT pan)    /* pan position 0 = full left; 127 = full right */
{
    short loff, roff;

    ENTER;
    /* set page register */
    IWL_SET_PAGE(voice);
    /* set pan position */
    loff = iw_atten_tab[(IW_ATTENTABSIZE-1)-pan];
    roff = iw_atten_tab[pan];
    if (pan != 127 && pan != 0) {
	loff >>= 1;
	roff >>= 1;
    }
    IWL_OUT_W(SET_LEFT_FINAL_OFFSET, loff<<4);
    IWL_OUT_W(SET_RIGHT_FINAL_OFFSET, roff<<4);
    LEAVE;
}

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

FUNCTION DEFINITION:
set_vol - ramp digital audio voice volume to new setting

DESCRIPTION:
set_vol sets a digital audio voice volume to a new volume by setting
up a voice volume ramp at the maximum possible rate.

RETURNS: void
*/
static void set_vol(
  struct dig_voice_status RFAR *vs,  /* voice data structure */
  USHORT voice,                      /* voice to set volume */
  int wait)				 /* wait for volumes to ramp */
{
    short old_volume;
    short ramp_vol;
    short vol = IW_MAX_VOLUME-vs->volume-iwl_digital_master_volume-iwl_digital_mute_atten;
    UCHAR vc;

    ENTER;
    if (vol < 0)
	vol = 0;
    /* set page register */
    IWL_SET_PAGE(voice);
    vs->volume_control &= ~(IWL_STOP|IWL_STOPPED);
    IWL_OUT_CONTROL(SET_VOLUME_CONTROL, vs->volume_control|IWL_STOP|IWL_STOPPED);
    /* set voice volume */
    OS_OUTPORTB(iwl_register_select, GET_VOLUME);
    old_volume = (unsigned short)OS_INPORTW(iwl_data_low) >> 4;
    IWL_OUT_B(SET_VOLUME_RATE, 7);
    ramp_vol = vol & 0xFF0;
    if (ramp_vol < (IW_MIN_OFFSET<<4))
	ramp_vol = IW_MIN_OFFSET<<4;
    if (ramp_vol > (IW_MAX_OFFSET<<4))
	ramp_vol = IW_MAX_OFFSET<<4;
    if ((old_volume&0xFF0) > ramp_vol) {
	vs->volume_control |= IWL_DIR_DEC;
	IWL_OUT_B(SET_VOLUME_START, ramp_vol>>4);
	IWL_OUT_B(SET_VOLUME_END, IW_MAX_OFFSET);
    }
    else if ((old_volume&0xFF0) < ramp_vol) {
	vs->volume_control &= ~IWL_DIR_DEC;
	IWL_OUT_B(SET_VOLUME_START, IW_MIN_OFFSET);
	IWL_OUT_B(SET_VOLUME_END, ramp_vol>>4);
    }
    else {
	LEAVE;
	return;
    }
    IWL_OUT_CONTROL(SET_VOLUME_CONTROL, vs->volume_control);
    if (wait) {
	do {
	    IWL_INP_B(GET_VOLUME_CONTROL, vc);
	}
	while ((vc & (IWL_STOP|IWL_STOPPED)) == 0)
	    ;
    }
    LEAVE;
}

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

FUNCTION DEFINITION:
set_freq - set the digital audio playback rate

DESCRIPTION:
set_freq sets the playback frequency for the voice indicated.

RETURNS: void
*/
static void set_freq(
  int voice,     /* voice to set playback rate */
  ULONG freq)    /* playback rate to set */
{
    USHORT fc;
    struct dig_voice_status *vs = &dig_voice_status[voice];

    /* calc fc register */
    fc = (unsigned short)(((freq<<10L)+22050UL) / 44100UL);
    /* set playback rate */
    ENTER;
    OS_OUTPORTB(iwl_page_register, voice);
    OS_OUTPORTB(iwl_register_select, SET_FREQUENCY);
    OS_OUTPORTW(iwl_data_low, fc);
    if (vs->type & IW_TYPE_STEREO) {
	OS_OUTPORTB(iwl_page_register, vs->extra_voice);
	OS_OUTPORTB(iwl_register_select, SET_FREQUENCY);
	OS_OUTPORTW(iwl_data_low, fc);
    }
    LEAVE;
}

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

FUNCTION DEFINITION:
init_playback - initialize a voice for playback

DESCRIPTION:
init_playback initializes the voice tracking structure for the digital
audio playback voice.

RETURNS: void
*/
static void init_playback(
  int voice) /* voice to initialize */
{
    struct dig_voice_status *vs = &dig_voice_status[voice];
    ULONG addr;
    int i;

    ENTER;
    for (i=0; i < MAX_BUFFS; i++) {
	vs->buffs[i].addr_s = 0;
	vs->buffs[i].play_size = 0;
	vs->buffs[i].status = 0;
    }
    vs->filled = 0;
    vs->insert_buf = 0;
    vs->insert_len = 0;
    vs->play_buf = 0;
    vs->insert_addr = vs->addr_s;
    if (vs->type & IW_TYPE_8BIT) {
	addr = vs->addr_s;
    }
    else {
	addr = iwl_convert_to_16bit(vs->addr_s);
    }
    set_addr_regs(voice, addr, SET_ACC_LOW, SET_ACC_HIGH);
    set_addr_regs(voice, addr, SET_START_LOW, SET_START_HIGH);
    LEAVE;
}



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

FUNCTION DEFINITION:
iwl_update_playback - set up voice to play half of DRAM buffer

DESCRIPTION:
iwl_update_playback set the digital audio playback voice to either play
through the mid point of the DRAM buffer or loop to the beginning when it
reaches the end of the DRAM buffer.

RETURNS: void
*/
static void iwl_update_playback(
  int voice) /* voice to update */
{
    ULONG addr;
    struct dig_voice_status *vs = &dig_voice_status[voice];
    unsigned short next_buf, buf;
    enum { STOP, THROUGH, LOOP }
    mode = STOP;

    ENTER;
#ifdef DEBUG
    dprintf("update ");
    draw(voice);
#endif
    vs->do_update = 0;
    buf = vs->play_buf;
    if ((vs->buffs[buf].status & BUFSTAT_FILLED) == 0) {
	LEAVE;
	return;
    }
    for (;;buf = next_buf) {
	next_buf = (buf + 1);
	if (next_buf == MAX_BUFFS)
	    next_buf = 0;
	if (vs->buffs[buf].status & BUFSTAT_INT) {
	    if (next_buf != vs->insert_buf && vs->buffs[next_buf].status & BUFSTAT_FILLED) {
		if (vs->buffs[buf].addr_s + vs->buffs[buf].play_size == vs->addr_e) {
		    mode = LOOP;
		}
		else {
		    mode = THROUGH;
		}
	    }
	    break;
	}
	if (next_buf == vs->insert_buf)
	    break;
    }
    addr = vs->buffs[buf].addr_s + vs->buffs[buf].play_size;
#ifdef DEBUG
    switch (mode) {
	case STOP:
	    dprintf("    STOP");
	    break;
	case THROUGH:
	    dprintf("    THROUGH");
	    break;
	case LOOP:
	    dprintf("    LOOP");
	    break;
    }
    dprintf(" at %lx\n", addr);
#endif
    if (!(vs->type & IW_TYPE_8BIT)) {
	addr = iwl_convert_to_16bit(addr);
    }
    addr--;
    switch (mode) {
	case STOP:
	    vs->voice_control &= ~IWL_LPE;
	    vs->volume_control &= ~IWL_ENPCM; /* make accum stop */
	    break;
	case THROUGH:
	    vs->voice_control &= ~IWL_LPE;
	    vs->volume_control |= IWL_ENPCM; /* make accum keep on going */
	    break;
	case LOOP:
	    vs->voice_control |= IWL_LPE;
	    vs->volume_control |= IWL_ENPCM; /* make accum loop */
	    break;
    }
    set_addr_regs(voice, addr, SET_END_LOW, SET_END_HIGH);
    if ((vs->status & STAT_MASK) != STAT_USER_PAUSED) {
	iw_synth_restart_digital(voice);
    }
    LEAVE;
}

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

FUNCTION DEFINITION:
preformat - preformat data for stereo playback

DESCRIPTION:
preformat uninterleaves the digital audio data in the data buffer from the
wrapper or application and places it in the DMA buffer. (Normally left and
right data is interleaved).

RETURNS: void
*/
static void preformat(
  USHORT voice, /* voice to preformat data */
  USHORT size,  /* number of bytes of data to preformat */
  int right)    /* preformat left or right channel 0=left; 1=right */
{
    ULONG i;
    USHORT ssize;
    unsigned char eightbit;

#if defined(_WINDOWS)
    char huge *src, huge *dst;
#else
    char RFAR *src, RFAR *dst;
    ULONG lsize;
    int cross;
#endif
    struct dig_voice_status *vs = &dig_voice_status[voice];
    eightbit = (unsigned char)((vs->type & IW_TYPE_8BIT) == IW_TYPE_8BIT);
    dst = vs->pc_stbuff->vptr;
    src = vs->pc_buffer;
    while (size) {
#if defined(__WATCOMC__) && defined(__FLAT__) || defined(_WINDOWS)
	ssize = size;
#else
	lsize = 65536UL - (ULONG)FP_OFF(src);
	if (lsize >= (ULONG)size) {
	    ssize = size;
	    cross = 0;
	}
	else {
	    ssize = (USHORT)lsize;
	    cross = 1;
	}
#endif
	if (right) {
	    src++;
	    if (!eightbit) {
		src++;
	    }
	}
	if (eightbit) {
	    for (i=ssize; i > 0; i--) {
		*dst++ = *src++;
		src++;
	    }
	}
	else {
	    for (i=ssize>>1; i > 0; i--) {
		*dst++ = *src++;
		*dst++ = *src++;
		src += 2;
	    }
	}
	size -= ssize;
#if !(defined(__WATCOMC__) && defined(__FLAT__)) && !defined(OS2) && !defined(_WINDOWS)
	if (cross) {
	    src = MK_FP(FP_SEG(src)+0x1000U, 0);
	}
#endif
    }
}

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

FUNCTION DEFINITION:
iwl_dig_dma_next_buffer - transfers data to DRAM

DESCRIPTION:
- if DMA channel is not busy
  - given the current DRAM insertion point, find if we are in the
    first or last half of the DRAM buffer
  - set up a DRAM tracking struct to track this set of data's DRAM
    start address, length, and status
  - copy the data to the DMA buffer (if the data is stereo it is
    uninterleaved and placed one of two DRAM buffers according
    to left or right)
  - transfer the data to the DRAM buffer, if we are placing data
    in the last half of the DRAM buffer, copy the last two bytes
    to the bytes before the beginning of the DRAM buffer to correct
    for data interpolation.
  - if the data does not have IW_TYPE_PRELOAD set the voice to play
    appropriately for the  DRAM half and start it

RETURNS: voice
*/
void iwl_dig_dma_next_buffer(
  int voice) /* voice to transfer data to DRAM */
{
    struct dig_voice_status *vs = &dig_voice_status[voice];
    ULONG size, st_size, addr_s, offset, end;
    register char stereo;

    ENTER;
    if (iw_dma_ready(IW_DMA_CHAN_1)) {
	stereo = (unsigned char)((vs->type & IW_TYPE_STEREO) == IW_TYPE_STEREO);
	if (vs->insert_addr < vs->addr_m) {
	    end = vs->addr_m;
	}
	else {
	    end = vs->addr_e;
	}
	size = end - vs->insert_addr;
	if (size > (vs->b_size - vs->filled)) {
	    size = vs->b_size - vs->filled;
	}
	if (stereo)
	    size <<= 1;
	if (vs->size < size) {
	    size = vs->size;
	}
	addr_s = vs->buffs[vs->insert_buf].addr_s = vs->insert_addr;
	if (stereo) {
	    st_size = size >> 1;
	}
	last_dma_voice = voice;
	vs->status &= ~DMASTAT_PENDING;
	vs->status |= DMASTAT_WORKING;
	if (stereo) {
	    preformat(voice, (USHORT)st_size, stereo_dma);
	    offset = stereo_dma ? (vs->st_offset) : 0;
#ifdef SYNTH_DIG_AUDIO_USE_DMA
	    iw_dram_xfer(vs->pc_stbuff, st_size, addr_s+offset, vs->dma_control, IW_DMA);
#else
	    if (st_size) {
	      iw_poke_block(vs->pc_stbuff->vptr, addr_s+offset, st_size, vs->dma_control);
	    }
	    iwl_flags |= F_GEN_TC;
#endif
	    if (stereo_dma) {
		vs->buffs[vs->insert_buf].status |= BUFSTAT_FILLED;
	    }
	    stereo_dma ^= 1;
	}
	else {
#if defined(__WATCOMC__) && defined(__FLAT__)
	    iwu_memcpy(vs->pc_stbuff->vptr, vs->pc_buffer, size);
#elif defined(_WINDOWS)
	    MemCopySrc((void far *)vs->pc_stbuff->vptr,
	     (void far *)vs->pc_buffer,
	     (USHORT)size);
#else
	    iwu_memcpy(vs->pc_stbuff->vptr, vs->pc_buffer, (USHORT)size);
#endif
#ifdef SYNTH_DIG_AUDIO_USE_DMA
	    iw_dram_xfer(vs->pc_stbuff, size, addr_s, vs->dma_control, IW_DMA);
#else
	    if (st_size) {
	      iw_poke_block(vs->pc_stbuff->vptr, addr_s, size, vs->dma_control);
	    }
	    iwl_flags |= F_GEN_TC;
#endif
	    vs->buffs[vs->insert_buf].status |= BUFSTAT_FILLED;
	}
	if (!stereo_dma) {
#if defined(_WINDOWS)
	    {
		unsigned char huge *data;

		data = (unsigned char huge *)vs->pc_buffer;
		data += size;
		vs->pc_buffer = (unsigned char RFAR *)data;
	    }
#elif !(defined(__WATCOMC__) && defined(__FLAT__)) && !defined(OS2)
	    {
		unsigned long addr;

		addr =  (ULONG)FP_SEG(vs->pc_buffer)*16L +
			(ULONG)FP_OFF(vs->pc_buffer) + size;
		vs->pc_buffer = MK_FP((USHORT)(addr>>4), (USHORT)(addr & 0x0F));
	    }
#else
	    vs->pc_buffer += size;
#endif
	    vs->size -= size;
	    vs->insert_len += size;
	    if (vs->size == 0) {
		/* when this buffer is done playing (voice int) then the */
		/* user buffer is done playing.  A VOICE_DONE signal */
		/* should be sent */
		vs->buffs[vs->insert_buf].status |= BUFSTAT_DONE;
	    }
	    if (stereo)
		size = st_size;
	    vs->insert_addr += size;
//		if (vs->insert_addr == vs->addr_m || vs->insert_addr == vs->addr_e) {
	    if ((((vs->addr_e - vs->insert_addr) >= vs->threshold) &&
	    (vs->insert_len >= vs->threshold)) ||
	    vs->addr_e == vs->insert_addr) {
		if (vs->insert_addr == vs->addr_e) {
		    vs->insert_addr = vs->addr_s;
		}
		vs->buffs[vs->insert_buf].status |= BUFSTAT_INT;
		vs->insert_len = 0;
	    }
	    vs->filled += size;
	    vs->buffs[vs->insert_buf].play_size = size;
	    vs->insert_buf = (vs->insert_buf + 1);
	    if (vs->insert_buf == MAX_BUFFS)
		vs->insert_buf = 0;
	    if (!(vs->type & IW_TYPE_PRELOAD)) {
		if (vs->status & STAT_STARTED) {
		    iwl_update_playback(voice);
		}
		else {
		    if ((vs->status & STAT_MASK) != STAT_USER_PAUSED) {
			iw_synth_start_digital(voice);
		    }
		}
	    }
	}
    }
    else {
	vs->status |= DMASTAT_PENDING;
    }
    LEAVE;
}

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

FUNCTION DEFINITION:
dig_check_db - check voice data buffer 

DESCRIPTION:
dig_check_db makes sure there is digital audio data from the wrapper
to be transferred to the DRAM on the card.  If there is room in the
DRAM for more digital data and we have data from the wrapper it is
transferred to DRAM.  If there is no data from the wrapper, this
routine issues a IW_DIG_MORE_DATA callback.  The wrapper can 
respond three ways to this:
  IW_DIG_MORE_DATA - the wrapper sent more data
  IW_DIG_PAUSE     - the wrapper did not send data and playback is
                     to be paused
  IW_DIG_DONE      - the wrapper has no more data to send

RETURNS: int - 1 = more data passed in or voice paused
               0 = done playing
*/
static int dig_check_db(int voice)
{
    struct dig_voice_status *vs = &dig_voice_status[voice];
    int ret_val;

#ifdef DEBUG
    dprintf("CHECK_DB: ");
#endif
    if (vs->filled < vs->b_size &&
	    (vs->buffs[vs->insert_buf].status & BUFSTAT_FILLED) == 0) {
	/* if a buffer is empty */
	if (vs->size) {
	    iwl_dig_dma_next_buffer(voice);
	    return(1);
	}
	if (vs->callback) {
	    OS_PUSH_DS();
	    ret_val = (*vs->callback)(IW_DIG_MORE_DATA, voice, &vs->pc_buffer, &vs->size);
	    OS_POP_DS();
	    if (vs->status == STAT_UNUSED)
		return(0);
	    switch (ret_val) {
		case IW_DIG_DONE:
#ifdef DEBUG
		    dprintf("DM done\n");
#endif
			/* this condition may occur during preloading of buffers */
			/* make sure application has enough data at start so that */
			/* stop_playback doesn't get called before playback begins */
		    if ((vs->status & STAT_MASK) == STAT_PAUSED) {
			iw_synth_stop_digital(voice);
		    }
		    return(0);
		case IW_DIG_MORE_DATA:
#ifdef DEBUG
		    dprintf("DM more  ");
#endif
		    if (vs->size) {
			iwl_dig_dma_next_buffer(voice);
		    }
		    return(1);
		case IW_DIG_PAUSE:
#ifdef DEBUG
		    dprintf("DM pause\n");
#endif
		    vs->status &= ~STAT_MASK;
		    vs->status |= STAT_PAUSED;
		    return(1);
	    }
	}
    }
    return(0);
}

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

FUNCTION DEFINITION:
dig_terminal_count - DMA terminal count handler

DESCRIPTION:
dig_terminal_count receives control when a DMA transfer to DRAM is 
completed.  This routine runs through all the other voices and starts 
a transfer on the next voice with a DMA transfer pending.

RETURNS: int - 0 = the DMA channel is busy
               1 = new transfer started
*/
#if defined(__BORLANDC__)
#pragma warn -par
#endif
static int dig_terminal_count(USHORT flags)
{
    struct dig_voice_status *vs;
    USHORT voice;
    USHORT first_voice;

    if (iw_dma_ready(IW_DMA_CHAN_1)) {
#ifdef DEBUG
//  dprintf("terminal count\n");
#endif
	/* round robin check for dma-ing next buffer */
	voice = last_dma_voice;
	if (stereo_dma) {
	    dig_check_db(voice);
	    return(1);
	}
	vs = &dig_voice_status[voice];
	vs->status &= ~(DMASTAT_WORKING|DMASTAT_PENDING);
	first_voice = last_dma_voice + 1;
	if (first_voice == iwl_voices)
	    first_voice = 0;
	for (vs=&dig_voice_status[first_voice],voice=first_voice;
		 voice < iwl_voices; voice++, vs++) {
	    if ((vs->status & STAT_MASK) == STAT_UNUSED)
		continue;
	    if (dig_check_db(voice))
		return(1);
	}
	for (vs=dig_voice_status, voice=0; voice < first_voice; voice++, vs++) {
	    if ((vs->status & STAT_MASK) == STAT_UNUSED)
		continue;
	    if (dig_check_db(voice))
		return(1);
	}
    }
    return(0);
}
#if defined(__BORLANDC__)
#pragma warn .par
#endif

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

FUNCTION DEFINITION:
dig_voice_handler - interrupt handler for digital audio playback voice

DESCRIPTION:
dig_voice_handler is the interrupt handler for digital audio playback
voices.  A digital audio playback voice is setup to interrupt when it
plays half of the data in DRAM.  This routine returns any completed
data buffers to the wrapper with a IW_DIG_BUFFER_DONE callback.

RETURNS:
*/
static int dig_voice_handler(int voice)
{
    struct dig_voice_status *vs = &dig_voice_status[voice];
    ULONG play_size;
    unsigned short buf, next_buf, last_buf;
    UCHAR restart = 0;
    UCHAR status, state;

    state = (UCHAR)(vs->status & STAT_MASK);
    if (state != STAT_UNUSED && voice != record_voice) {
#ifdef DEBUG
	dprintf("VI   ");
#endif
	buf = vs->play_buf;
	last_buf = vs->insert_buf;
	do {
	    next_buf = buf + 1;
	    if (next_buf == MAX_BUFFS) next_buf = 0;
	    status = vs->buffs[buf].status;
	    play_size = vs->buffs[buf].play_size;
	    vs->filled -= play_size;
	    if (vs->type & IW_TYPE_STEREO) play_size <<= 1;
	    vs->position += play_size;
	    vs->buffs[buf].status = 0;
	    vs->play_buf = buf = next_buf;
	    if (status & BUFSTAT_DONE && vs->callback) {
#ifdef DEBUG
		dprintf("IW_DIG_BUFFER_DONE\n");
#endif
		OS_PUSH_DS();
		((*vs->callback)(IW_DIG_BUFFER_DONE, voice, 0L, 0L));
		OS_POP_DS();
		if (vs->status == STAT_UNUSED) return(0);
	    }
	} while ((status & BUFSTAT_INT) == 0 && next_buf != last_buf) ;
	restart = (UCHAR)((status & BUFSTAT_INT) && next_buf != last_buf && vs->buffs[buf].status & BUFSTAT_FILLED);
	vs->do_update = 1;
	if (!restart) {
			/* clear irq present bit */
	    vs->status &= ~STAT_MASK;
	    vs->status |= STAT_PAUSED;
	    vs->voice_control |= (IWL_STOP|IWL_STOPPED);
	    iwl_dig_change_voice(voice, 0);
	}
	if ((vs->status & (DMASTAT_PENDING|DMASTAT_WORKING)) ||
		(dig_check_db(voice) || restart)) {
	    if (vs->do_update) {
		iwl_update_playback(voice);
	    }
	    return(1);
	}
	if (state == STAT_PLAYING) {
	    iw_synth_stop_digital(voice);
	    return(0);
	}
	else {
	    return(1);
	}
    }
    return(0);
}

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

FUNCTION DEFINITION:
iw_synth_dig_set_vol - set the volume for a digital audio voice

PARAMETERS:
	voice - identifies the synth voice being used for playback.
			(this will be returned to the wrapper on the call to
			iw_synth_play_digital).
    vol   - new volume setting for voice 0 - 127

RETURNS: void
*/
void iw_synth_dig_set_vol(
  USHORT voice,  /* voice to set volume */
  short vol)    /* volume setting for voice */
{
    struct dig_voice_status *vs = &dig_voice_status[voice];

    ENTER;
    if ((vs->status & STAT_MASK) != STAT_UNUSED) {
	vs->volume = IWL_TO_FMM(iw_atten_tab[vol]);
	if (vs->volume > IW_MAX_VOLUME) vs->volume = IW_MAX_VOLUME;
	set_vol(vs, voice, 0);
	if (vs->type & IW_TYPE_STEREO) {
	    set_vol(vs, vs->extra_voice, 0);
	}
    }
    LEAVE;
}

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

FUNCTION DEFINITION:
iw_synth_dig_master_vol - set the volume of all digital audio voices

NOTE: volumes should be between 0 - 127

RETURNS: void
*/
void iwl_dig_synth_master_vol(void)
{
    struct dig_voice_status *vs;
    int i;

    ENTER;
    vs = dig_voice_status;
    for (i=0; i < IW_MAX_VOICES; i++, vs++) {
	if (vs->status != STAT_UNUSED) {
	    set_vol(vs, i, 0);
	    if (vs->type & IW_TYPE_STEREO) {
		set_vol(vs, vs->extra_voice, 0);
	    }
	}
    }
    LEAVE;
}

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

FUNCTION DEFINITION:
iw_synth_dig_set_pan - set the pan for a voice used for digital audio playback

DESCRIPTION:
iw_dig_set_pan set the pan value for the voice indicated if the voice is
being used for digital audio and the digital audio data is mono.

PARAMETERS:
	voice - identifies the synth voice being used for playback.
			(this will be returned to the wrapper on the call to
			iw_synth_play_digital).
    pan   - pan position 0 = full left; 127 = full right

RETURNS: void
*/
void iw_synth_dig_set_pan(
  USHORT voice, /* voice to set pan value */
  USHORT pan)   /* pan position 0 = full left; 127 = full right */
{
    struct dig_voice_status *vs = &dig_voice_status[voice];

    ENTER;
    if ((vs->status & STAT_MASK) != STAT_UNUSED) {
	if (!(vs->type & IW_TYPE_STEREO)) {
	    set_pan(voice, pan);
	}
    }
    LEAVE;
}


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

FUNCTION DEFINITION:
iw_synth_dig_set_freq - set the digital audio playback rate

DESCRIPTION:
iwl_dig_set_freq sets the playback frequency for the voice indicated if
the voice is currently being used for digital audio playback.

PARAMETERS:
	voice - identifies the synth voice being used for playback.
			(this will be returned to the wrapper on the call to
			iw_synth_play_digital).
	frequency - new playback frequency in hertz

RETURNS: void
*/
void iw_synth_dig_set_freq(
  USHORT voice,     /* voice to set playback rate */
  USHORT frequency) /* playback rate to set */
{
    struct dig_voice_status *vs = &dig_voice_status[voice];

    ENTER;
    if ((vs->status & STAT_MASK) != STAT_UNUSED) {
	vs->rate = frequency;
	set_freq(voice, frequency);
    }
    LEAVE;
}

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

FUNCTION DEFINITION:
iwl_dig_change_voice - enable or disable interrupts for a voice

DESCRIPTION:
iwl_dig_change_voice enable or disable interrupts for a digital audio
voice. 

RETURNS: void
*/
void iwl_dig_change_voice(
  int voice, /* voice to enable interrupts */
  int ints)  /* 0=disable; 1=enable interrupts */
{
    unsigned char vc;
    struct dig_voice_status *vs;

    vs = &dig_voice_status[voice];
    vc = vs->voice_control;
    OS_OUTPORTB(iwl_page_register, voice);
    OS_OUTPORTB(iwl_register_select, SET_VOLUME_CONTROL);
    OS_OUTPORTB(iwl_data_high, vs->volume_control);
    iw_delay();
    OS_OUTPORTB(iwl_data_high, vs->volume_control);
    OS_OUTPORTB(iwl_register_select, SET_CONTROL);
    if (ints) {
	OS_OUTPORTB(iwl_data_high, vc|IWL_IRQE);
	iw_delay();
	OS_OUTPORTB(iwl_data_high, vc|IWL_IRQE);
    }
    else {
	OS_OUTPORTB(iwl_data_high, vc);
	iw_delay();
	OS_OUTPORTB(iwl_data_high, vc);
    }
    if (vs->type & IW_TYPE_STEREO) {
	OS_OUTPORTB(iwl_page_register, vs->extra_voice);
	OS_OUTPORTB(iwl_register_select, SET_VOLUME_CONTROL);
	OS_OUTPORTB(iwl_data_high, vs->volume_control);
	iw_delay();
	OS_OUTPORTB(iwl_data_high, vs->volume_control);
	OS_OUTPORTB(iwl_register_select, SET_CONTROL);
	OS_OUTPORTB(iwl_data_high, vc);
	iw_delay();
	OS_OUTPORTB(iwl_data_high, vc);
    }
}

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

FUNCTION DEFINITION:
iw_synth_pause_digital - pause a digital audio playback voice

DESCRIPTION:
iw_synth_pause_digital responds to the iw_pause_digital call when the voice
indicated is used for digital audio playback through the codec.  This 
routine stops all activity on the voice and disables voice interrupts.
However, no playback resources are freed.

PARAMETERS:
	voice - identifies the synth voice being used for playback.
			(this will be returned to the wrapper on the call to
			iw_synth_play_digital).

RETURNS: void
*/
void iw_synth_pause_digital(
  int voice) /* voice to pause playback */
{
    struct dig_voice_status *vs;

    vs = &dig_voice_status[voice];
    vs->status &= ~STAT_MASK;
    vs->status |= STAT_USER_PAUSED;
    ENTER;
    OS_OUTPORTB(iwl_page_register, voice);
    LEAVE;
    vs->voice_control |= (IWL_STOP|IWL_STOPPED);
    iwl_dig_change_voice(voice, 0);
}

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

FUNCTION DEFINITION:
iw_synth_restart_digital - restart digital audio playback voice

DESCRIPTION:
iw_synth_restart_digital responds to the iw_restart_digital call when the voice
indicated is used for digital audio playback through the synthesizer.  This 
routine restarts the voice and enables voice interrupts.  This routine is
used to restart a voice paused with iw_synth_pause_digital.

PARAMETERS:
	voice - identifies the synth voice being used for playback.
			(this will be returned to the wrapper on the call to
			iw_synth_play_digital).

RETURNS: void
*/
void iw_synth_restart_digital(
  int voice) /* voice to restart playback */
{
    struct dig_voice_status *vs = &dig_voice_status[voice];

    ENTER;
    /* start voice */
    if ((vs->status & STAT_MASK) == STAT_PAUSED) {
	ULONG addr;
	addr = vs->buffs[vs->play_buf].addr_s;
	if (!(vs->type & IW_TYPE_8BIT)) {
	    addr = iwl_convert_to_16bit(addr);
	}
	set_addr_regs(voice, addr, SET_ACC_LOW, SET_ACC_HIGH);
    }
    vs->voice_control &= ~(IWL_STOP|IWL_STOPPED);
    iwl_dig_change_voice(voice, 1);
    vs->status &= ~STAT_MASK;
    vs->status |= STAT_PLAYING;
    LEAVE;
}

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

FUNCTION DEFINITION:
iw_synth_stop_digital - stop digital audio playback

DESCRIPTION:
iw_synth_stop_digital responds to the iw_stop_digital call when the voice
specified is being used for digital audio playback through the synthesizer.
This routine shuts down the activity on the playback voice, and stops all
DMA transfers that may be in progress.  After all activity is stopped
the voice is freed.

PARAMETERS:
	voice - identifies the synth voice being used for playback.
			(this will be returned to the wrapper on the call to
			iw_synth_play_digital).

RETURNS: void
*/
void RFAR iw_synth_stop_digital(
  int voice) /* voice to stop digital audio playback */
{
    struct dig_voice_status *vs = &dig_voice_status[voice];

    ENTER;
    if ((vs->status & STAT_MASK) != STAT_UNUSED) {
	if (vs->status & DMASTAT_WORKING) {
	    iw_stop_dma(IW_DMA_CHAN_1);
	    if (stereo_dma)
		stereo_dma = 0;
	}
	vs->volume_control = IWL_STOP|IWL_STOPPED;
	vs->voice_control = IWL_STOP|IWL_STOPPED;
	iwl_dig_change_voice(voice, 0);
	vs->volume = iw_atten_tab[0];
	if (vs->type & IW_TYPE_STEREO) {
	    set_vol(vs, vs->extra_voice, 0);
	}
	set_vol(vs, voice, 1); /* ramp volume down */
	if (vs->callback) {
	    OS_PUSH_DS();
#ifdef DEBUG
//		dprintf("IW_DIG_DONE\n");
#endif
	    (*vs->callback)(IW_DIG_DONE, voice, 0L, 0L);
	    OS_POP_DS();
	}
	vs->status = STAT_UNUSED;
	iw_free_voice(voice);
	if (vs->type & IW_TYPE_STEREO) {
	    iw_free_voice(vs->extra_voice);
	}
    }
    LEAVE;
}

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

FUNCTION DEFINITION:
iw_synth_start_digital - start digital audio playback

DESCRIPTION:
iw_synth_start_digital responds to the iw_start_digital when the voice indicated
is used for digital audio playback through the synthesizer.  This routine
is normally used after digital audi playback has been set up with a data
type of IW_TYPE_PRELOAD.  This routine sets the playback voice volumes and
starts the voice running.

PARAMETERS:
	voice - identifies the synth voice being used for playback.
			(this will be returned to the wrapper on the call to
			iw_synth_play_digital).

RETURNS: void
*/
void iw_synth_start_digital(int voice)
{
    struct dig_voice_status *vs = &dig_voice_status[voice];

    ENTER;
    vs->type &= ~IW_TYPE_PRELOAD;
    vs->status |= STAT_STARTED;
    set_freq(voice, vs->rate);
    set_vol(vs, voice, 0);
    if (vs->type & IW_TYPE_STEREO) {
	set_vol(vs, vs->extra_voice, 0);
    }
    /* set end points, rate, and start voice */
    if (!(vs->status & DMASTAT_WORKING) || !stereo_dma) {
	iwl_update_playback(voice);
    }
    LEAVE;
}

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

FUNCTION DEFINITION:
iw_synth_play_next_buffer - send a new buffer with digital audio data

DESCRIPTION:
iw_synth_play_next_buffer accepts a new buffer with digital audio data if the 
buffer currently being played is now empty.

PARAMETERS:
	voice - identifies the synth voice being used for playback.
			(this will be returned to the wrapper on the call to
			iw_synth_play_digital).
	buff -	pointer to a buffer with new digital audio data
	size -  size of the new buffer

RETURNS: int - IW_OK new buffer was accepted for playback
               IW_DMA_BUSY new buffer cannot be accepted, playback in progress
*/
int iw_synth_play_next_buffer(
  int voice,        /* voice to receive buffer with digital audio data */
  UCHAR RFAR *buff, /* buffer with new digital audio data */
  ULONG size)       /* size of data buffer */
{
    struct dig_voice_status *vs;

    ENTER;
    vs = &dig_voice_status[voice];
    if (vs->size == 0) {
	vs->pc_buffer = buff;
	vs->size = size;
	if (vs->filled < vs->b_size &&
		(vs->buffs[vs->insert_buf].status & BUFSTAT_FILLED) == 0) {
	    iwl_dig_dma_next_buffer(voice);
	}
	LEAVE;
	return(IW_OK); /* ok */
    }
    LEAVE;
    return(IW_DMA_BUSY); /* DMA or playback currently in progress, 
							can't accept new buff */
}

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

FUNCTION DEFINITION:
iw_synth_play_digital - initiate digital audio playback through the synth

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

PARAMETERS:
	priority -  priority at which a voice is to be allocated for
				digital audio playback.  This should normally
				be zero to avoid voice stealing.
	buffer -	pointer to a buffer containing digital audio
				data to be played.
	size -		size of digital audio data buffer.
	iwl_addr -	address of DRAM buffer from which data will be
				played.  This should be returned from the call
				to iw_malloc.
	volume -	volume at which to play digital audio data.  This
				should be between 0 and 127.
	pan -		pan position of the data in the stereo field.
				0 = full left, 127 = full right.  This parameter
				is only used with mono data.
	frequency - rate at which to playback the audio data
	type -		format of audio 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 - a pointer to a structure containing information
					about a buffer in PC memory that will be used
					to uninterleave stereo data.
	callback -	address of a function that will receive notifications
				on the progress of digital audio playback.

RETURNS: int - voice number used for synthesizer playback
               IW_NO_MORE_VOICES if all synth voices are in use
*/
int iw_synth_play_digital(
  USHORT priority,
  UCHAR RFAR *buffer,
  ULONG size,
  ULONG iwl_addr,
  USHORT volume,
  USHORT pan,
  USHORT frequency,
  UCHAR type,
  struct iw_dma_buff RFAR *st_work_buff,
  int (RFAR *callback)(int, int, UCHAR RFAR * RFAR *, ULONG RFAR *))
{
    void (RFAR *callback_addr)(int);
    int voice;
    USHORT dma_control = 0;
    struct iwl_mem_block m;
    struct dig_voice_status *vs;

    ENTER;
#if defined (__BORLANDC__) && NEARCODE == 1
    asm mov word ptr callback_addr, offset iw_synth_stop_digital
    asm mov word ptr callback_addr+2, cs
#else
    callback_addr = iw_synth_stop_digital;
#endif
    voice = iw_allocate_voice(priority, callback_addr);
    if (voice == IW_NO_MORE_VOICES) {
	LEAVE;
	return(voice);
    }
    IWL_SET_PAGE(voice);
    IWL_OUT_B(SET_VOICE_MODE, IWL_SMSI_OFFEN);
    vs = &dig_voice_status[voice];
    if (type & IW_TYPE_STEREO) {
	vs->extra_voice = iw_allocate_voice(priority, callback_addr);
	if (vs->extra_voice == IW_NO_MORE_VOICES) {
	    iw_free_voice(voice);
	    LEAVE;
	    return(vs->extra_voice);
	}
	IWL_SET_PAGE(vs->extra_voice);
	IWL_OUT_B(SET_VOICE_MODE, IWL_SMSI_OFFEN);
    }
    vs->type = type;
    vs->status = STAT_PAUSED;
    vs->size = 0;
    vs->pc_buffer = 0L;
    if (type & IW_TYPE_INVERT_MSB) {
	dma_control |= IW_DMA_INVERT_MSB;
    }
    vs->dma_control = (UCHAR)dma_control;
    vs->position = 0;
    iwl_read_block(iwl_addr-32, &m);
    vs->addr_s = iwl_addr;
    vs->b_size = m.size-32;         /* full buffer size for sound data */
    /* 32 reserved for header, 32 reserved for stereo buffer */
    if (type & IW_TYPE_STEREO) {
	vs->b_size >>= 1;   /* stereo buffers are half of mono buffers */
	vs->st_offset = vs->b_size;
    }
    vs->addr_m = iwl_addr + (vs->b_size >> 1);
    vs->addr_e = iwl_addr + vs->b_size;
    vs->volume = IWL_TO_FMM(iw_atten_tab[volume]);
    if (vs->volume > IW_MAX_VOLUME) vs->volume = IW_MAX_VOLUME;
    vs->volume_control = IWL_STOPPED|IWL_STOP;
    if (type & IW_TYPE_8BIT) {
	vs->voice_control = 0;
    }
    else {
	vs->voice_control = IWL_WT16;
    }
    vs->callback = callback;
    vs->rate = frequency;
//  vs->threshold = frequency / 128;
    vs->threshold = 128;
    if (vs->type & IW_TYPE_STEREO) {
	vs->threshold <<= 1;
    }
    if (!(vs->type & IW_TYPE_8BIT)) {
	vs->threshold <<= 1;
    }
    init_playback(voice);
    set_freq(voice, vs->rate);
    vs->pc_stbuff = st_work_buff;   /* stereo work buffer (dma buffer) */
    if (vs->type & IW_TYPE_STEREO) {
	set_pan(voice, 0);
	set_pan(vs->extra_voice, 127);
    }
    else {
	set_pan(voice, pan);
    }
    if (size) {
	iw_synth_play_next_buffer(voice, buffer, size);
    }
    LEAVE;
    return(voice);
}

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

FUNCTION DEFINITION:
iw_synth_digital_position - get number of bytes played

DESCRIPTION:
iw_synth_digital_position returns the number of bytes played since the
last call to iw_synth_start_digital.

PARAMETERS:
	voice - identifies the synth voice being used for playback.
			(this will be returned to the wrapper on the call to
			iw_synth_play_digital).

RETURNS: unsigned long number of bytes played
*/
ULONG iw_synth_digital_position(int voice)
{
    struct dig_voice_status *vs = &dig_voice_status[voice];
    USHORT low, high, high1, status;
    ULONG addr;
    ULONG position;
    ULONG start;
    ULONG size;
    ENTER;
    if ((status=(vs->status & STAT_MASK)) != STAT_UNUSED) {
	if (status == STAT_PLAYING && vs->buffs[vs->play_buf].status != 0) {
	    OS_OUTPORTB(iwl_page_register, voice);
	    do {
		OS_OUTPORTB(iwl_register_select, GET_ACC_HIGH);
		high = OS_INPORTW(iwl_data_low);
		OS_OUTPORTB(iwl_register_select, GET_ACC_LOW);
		low = OS_INPORTW(iwl_data_low);
		OS_OUTPORTB(iwl_register_select, GET_ACC_HIGH);
		high1 = OS_INPORTW(iwl_data_low);
	    } while (high != high1) ;
	    addr = iwl_make_physical_address(low, high, (vs->type & IW_TYPE_8BIT) ? 0 : 1);
	    start = vs->buffs[vs->play_buf].addr_s;
	    size = vs->buffs[vs->play_buf].play_size;
	    if (addr <= start) {
		position = size;
	    } else {
		position = addr - start;
	    }
	    if (vs->type & IW_TYPE_STEREO) position <<= 1;
	    position += vs->position;
	} else {
	    position = vs->position;
	}
    } else {
	position = 0;
    }
    LEAVE;
    return(position);
}
#endif
