/*---------------------------------------------------------------------------*\

    FILE....: PLAYREC.CPP
    TYPE....: C++ Module
    AUTHOR..: David Rowe
    DATE....: 9/2/98
    AUTHOR..: Ron Lee
    DATE....: 9/11/06

    This module implements the play and record functions for the VPB API.


         Voicetronix Voice Processing Board (VPB) Software
         Copyright (C) 1999-2008 Voicetronix www.voicetronix.com.au

         This library is free software; you can redistribute it and/or
         modify it under the terms of the GNU Lesser General Public
         License as published by the Free Software Foundation; either
         version 2.1 of the License, or (at your option) any later version.

         This library is distributed in the hope that it will be useful,
         but WITHOUT ANY WARRANTY; without even the implied warranty of
         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
         Lesser General Public License for more details.

         You should have received a copy of the GNU Lesser General Public
         License along with this library; if not, write to the Free Software
         Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
         MA  02110-1301  USA

\*---------------------------------------------------------------------------*/

#include "playrec.h"

#include "apifunc.h"
#include "mapdev.h"
#include "generic.h"
#include "v4logagc.h"
#include "mess.h"

extern "C" {
#include "alawmulaw.h"
}
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cerrno>
#include <climits>

using std::string;

//! @defgroup PlayRecImpl Audio playback and recording implementation
//! @ingroup InternalImpl
//! @brief Implementation support found in @c playrec.cpp
//!@{
//{{{

// mode flags
#define PLAYREC_WAV    0
#define PLAYREC_VOX    1
#define PLAYREC_SYNC   0
#define PLAYREC_ASYNC  1

// record delay buffer to help with removing trailing DTMF
// set by experiment, tested across linear, alaw, mulaw
#define REC_DLY         2500


//! Base class for play/record state
struct Audio
{ //{{{
	string          term_digits;             //!< digits which terminate collection
	AudioCompress   mode;                    //!< current play mode
	AudioState      state;                   //!< current state
	float           hw_gain;                 //!< hardware gain parameter
	float           sw_gain;                 //!< software gain parameter
	float           linear_gain;             //!< linear gain
	pthread_mutex_t mutex;                   //!< global state protection
	pthread_cond_t  cond;                    //!< state change signalling

    //! @name Constructors
    //@{ //{{{

	//! Default constructor
	Audio()
	    : mode( VPB_LINEAR )
	    , state( VPB_AUDIO_IDLE )
	    , hw_gain( 0.0 )
	    , sw_gain( 0.0 )
	    , linear_gain( 0.0 )
	{
		pthread_mutex_init(&mutex, NULL);
		pthread_cond_init(&cond, NULL);
	}

	//! Default destructor
	~Audio()
	{
		pthread_mutex_destroy(&mutex);
		pthread_cond_destroy(&cond);
	}

    //@} //}}}

}; //}}}

//! @c Audio playback state data
struct Play : public Audio
{ //{{{
	//! Container type for a list of playback states.
	typedef std::vector<Play>   List;

	//! Container for a list of playback states.
	static List                 list;
}; //}}}
Play::List Play::list;

//! @c Audio recording state data
struct Record : public Audio
{ //{{{
	//! Container type for a list of recording states.
	typedef std::vector<Record> List;

	//! Container for a list of recording states.
	static List                 list;

	unsigned long   time_out;	//!< Maximum time in ms to record for.
	unsigned long   time_start;	//!< Starting time for measuring @a time_out.
	float	        rgaindown;	//!< Record Gain Down control for DTMF
	void           *v4log_agcstate; //!< AGC state (V4LOG card only)
	char            delay[REC_DLY]; //!< delay line for rec with dtmf termination

    //! @name Constructor
    //@{ //{{{

	//! Default constructor
	Record()
	    : time_out( 0 )
	    , time_start( 0 )
	    , rgaindown( 0.0 )
	    , v4log_agcstate( NULL )
	{
		memset(delay, 0, REC_DLY);
	}

    //@} //}}}

}; //}}}
Record::List Record::list;

//! Data for @c play_file_async_thread()
struct PLAY_FILE_ASYNC_ARGS {
	int             handle;
	WFILE          *wout;
	FILE           *vout;
	size_t          bytes;
	int             data;
	int             file_type;
};

//! Data for @c record_file_async_thread()
struct RECORD_FILE_ASYNC_ARGS {
	int	        handle;
	WFILE	       *win;
	FILE	       *vin;
	size_t          bytes;
	int             data;
	int             file_type;
};

//!@} //}}}


static pthread_attr_t   detached_thread;

static int         sleepms=20;	// time in ms to sleep between polling DSP

static float       words2bytes[] = {2,1,1,0.5};
static float       bytes2words[] = {0.5,1,1,2};


// diagnostics
//#define USE_PLAYREC_DIAG 1
#if USE_PLAYREC_DIAG
static unsigned short	fplay_min;  // minimum words in DSP play (write) buffer
static unsigned short	frec_max;   // maximum words in DSP record (read) buffer
#endif


// Internal version: Sets the hardware (TS5070 codec) play gain component.
static void play_set_hw_gain(int handle, float gain, Comm *c)
{ //{{{
	// gain is in range of +/- 12dB for TS5070

	if(gain > 12.0 || gain < -12.0)
		throw Wobbly(VPBAPI_GAIN_OUT_OF_RANGE);

	// Save a local copy for vpb_reset
	Play::list[handle].hw_gain = gain;

	// scale float gain to 8 bit unsigned int
	// each unit is 0.1 dB, 0dB is 0x80

	unsigned short  b,ch;
	maphndletodev(handle, &b, &ch);

	uint16_t mess[PC_LCODEC_PLAYGAIN] = { PC_LCODEC_PLAYGAIN,
					      PC_CODEC_PLAYGAIN,
					      ch,
					      (uint16_t)(gain * 10.0 + 0x80)
					    };
	c->PutMessageVPB(b,mess);
} //}}}

void play_reset_hw_gain( int handle )
{ //{{{
	play_set_hw_gain(handle, Play::list[handle].hw_gain, vpb_c);
} //}}}

// Internal version. Sets the TS5070 codec record gain.
static void record_set_hw_gain(int handle, float gain, Comm *c)
{ //{{{
	unsigned short	b,ch;

	maphndletodev(handle, &b, &ch);
	if(gain > 12.0 || gain < -12.0){
		mprintf("[%d/%d] record_set_hw_gain: -12 > [%f] > 12\n",
			 b, ch, gain);
		throw Wobbly(VPBAPI_GAIN_OUT_OF_RANGE);
	}

	// Save a local copy for vpb_reset
	Record::list[handle].hw_gain = gain;

	uint16_t mess[PC_LCODEC_RECGAIN] = { PC_LCODEC_RECGAIN,
					     PC_CODEC_RECGAIN,
					     ch
					   };

	// scale float gain to 8 bit unsigned int
	// each unit is 0.1 dB, 0dB is 0x80
	if( c->vpbreg(b)->ddmodel == DD_PCI ) gain = -gain;

	mess[3] = (uint16_t)(gain * 10.0 + 0x80);

	c->PutMessageVPB(b,mess);
} //}}}

void record_reset_hw_gain( int handle )
{ //{{{
	record_set_hw_gain(handle, Record::list[handle].hw_gain, vpb_c);
} //}}}


void playrec_open(unsigned int numch)
{ //{{{
	Play::list.resize(numch);
	Record::list.resize(numch);

	pthread_attr_init(&detached_thread);
	pthread_attr_setdetachstate(&detached_thread, PTHREAD_CREATE_DETACHED);

	mprintf("Init PlayRec module for %d channels ...\n", numch);

	for(unsigned int i=0; i < numch; ++i)
	{
		unsigned short  bd,ch;
		maphndletodev(i, &bd, &ch);

		int      model  = get_board_type(bd);
		VPBREG  *vr     = vpb_c->vpbreg(bd);
		Play    &play   = Play::list[i];
		Record  &record = Record::list[i];

		play.hw_gain     = vr->defPlayGain;
		record.rgaindown = vr->defRecordGainDown;
		record.hw_gain   = vr->defRecordGain;

		if(vr->useconf){
			switch( model ) {
			    case VPB_V4PCI:
			    {
				ANA_IFACE &iface = ((OL_CARD*)(vr->cardinfo))
						    ->iface[ch];

				if( iface.settings & MOD_HWPG )
					play.hw_gain = iface.hw_play_gain;

				if( iface.settings & MOD_PG )
					play.sw_gain = iface.play_gain;

				if( iface.settings & MOD_HWRG )
					record.hw_gain = iface.hw_rec_gain;

				if( iface.settings & MOD_RG )
					record.sw_gain = iface.rec_gain;

				break;
			    }
			    case VPB_PRI:
			    {
				OP_CARD *opcard = (OP_CARD*)vr->cardinfo;

				if( opcard->settings & MOD_HWPG )
					play.hw_gain = opcard->hw_play_gain;

				if( opcard->settings & MOD_HWRG )
					record.hw_gain = opcard->hw_rec_gain;

				if( opcard->settings & MOD_PG )
					play.sw_gain = opcard->play_gain;

				if( opcard->settings & MOD_RG )
					record.sw_gain = opcard->rec_gain;

				break;
			    }
			}
		}

		/* If the card is a VTCore based card, then check the config */
		switch( model ) {
		    case VPB_OPCI:
		    case VPB_OSW:
		    {
			//mprintf("playrec_open: Checking for extra VTCore config [%d]\n",
			//	  model);
			if( get_port_type(bd, ch) == VPB_FXS ){
				//mprintf("[%d/%d] Setting Station gain plan..\n", bd, ch);
				play.hw_gain   = vr->defSPlayGain;
				record.hw_gain = vr->defSRecordGain;
			}

			float   playgain         = 0.0;
			float   recordgain       = 0.0;
			float   hwplaygain       = 0.0;
			float   hwrecordgain     = 0.0;
			int     got_playgain     = 0;
			int     got_recordgain   = 0;
			int     got_hwplaygain   = 0;
			int     got_hwrecordgain = 0;

			VTCORE_CARD *card = (VTCORE_CARD*)vr->cardinfo;
			llc_var     *var  = card ? (llc_var*)card->config : NULL;

			while(var){
				if (strcasecmp(var->name,"playgain")==0){
					got_playgain++;
					playgain = atof(var->value);
					//mprintf("playrec_open: got playgain = %.2f\n",
					//	  playgain);
				}
				else if (strcasecmp(var->name,"recordgain")==0){
					got_recordgain++;
					recordgain = atof(var->value);
					//mprintf("playrec_open: got recordgain = %.2f\n",
					//	  recordgain);
				}
				else if (strcasecmp(var->name,"hwplaygain")==0){
					got_hwplaygain++;
					hwplaygain = atof(var->value);
					//mprintf("playrec_open: got hwplaygain = %.2f\n",
					//	  hwplaygain);
				}
				else if (strcasecmp(var->name,"hwrecordgain")==0){
					got_hwrecordgain++;
					hwrecordgain = atof(var->value);
					//mprintf("playrec_open: got hwrecordgain = %.2f\n",
					//	  hwrecordgain);
				}
				else if (strcasecmp(var->name,"chan")==0){
					int tmp = atoi(var->value);
					if (tmp == ch){
						//mprintf("[%d/%d] playrec_open: set values\n",
						//	bd, ch);
						if (got_hwplaygain)
							play.hw_gain = hwplaygain;
						if (got_playgain)
							play.sw_gain = playgain;
						if (got_hwrecordgain)
							record.hw_gain = hwrecordgain;
						if (got_recordgain)
							record.sw_gain = recordgain;
					}
				}

				if(var->next)
					var = var->next;
				else
				var = NULL;
			}
		    }
		}

		// set linear gain factor
		play.linear_gain   = powf(10.0, play.sw_gain / 20.0);
		record.linear_gain = powf(10.0, record.sw_gain / 20.0);

		// Setting Codec Hybrid Balances
		switch( model ) {
		    case VPB_V4PCI:
		    {
			//mprintf("playrec.cpp: Setting codec balances...\n");
			int tmp = 0;
			if(vr->useconf){
				//mprintf("playrec.cpp: Checking config file..\n");
				uint16_t tmpbal1 = vr->defbal1;
				uint16_t tmpbal2 = vr->defbal2;
				uint16_t tmpbal3 = vr->defbal3;
				ANA_IFACE &iface = ((OL_CARD*)(vr->cardinfo))
						    ->iface[ch];

				if( iface.settings & MOD_BALS){
					tmpbal1 = iface.codec_hyb_bal1;
					tmpbal2 = iface.codec_hyb_bal2;
					tmpbal3 = iface.codec_hyb_bal3;
					tmp = 1;
					mprintf("playrec.cpp: Using Codec Bal from config\n");
				}
				if (tmp==1){
					set_codec_reg(i,0x32,tmpbal1, vpb_c);
					set_codec_reg(i,0x3a,tmpbal2, vpb_c);
					set_codec_reg(i,0x42,tmpbal3, vpb_c);
				}
			}
			if( !tmp ) {
				// defaults havent been overwritten
				// so set them here based on version
				// we have to set defs here as eeprom isnt
				// read until after vpbreg code is
				// executed

				float revision = atof(vpb_c->vpbreg(bd)->revision);
				if( revision > 19.0 ) {
					mprintf("playrec.cpp: setting V%02.2f hyb bals on OL\n",
						revision);
					vpb_c->vpbreg(bd)->defbal1 = 0xec;
					vpb_c->vpbreg(bd)->defbal2 = 0x70;
					vpb_c->vpbreg(bd)->defbal3 = 0xf1;
				}
				set_codec_reg(i,0x32,vpb_c->vpbreg(bd)->defbal1, vpb_c);
				set_codec_reg(i,0x3a,vpb_c->vpbreg(bd)->defbal2, vpb_c);
				set_codec_reg(i,0x42,vpb_c->vpbreg(bd)->defbal3, vpb_c);
			}
			break;
		    }
		    case VPB_V4LOG:
			// default hybrid balance (V4PCI only)
			set_codec_reg(i, 0x32, 199, vpb_c);
			// logging card AGC
			v4log_agc_open(&record.v4log_agcstate);
			break;
		}
	}

      #if USE_PLAYREC_DIAG
	fplay_min = 64000;
	frec_max = 0;
      #endif
} //}}}


static void mutex_cleanup(void *p)
{ //{{{
	pthread_mutex_unlock((pthread_mutex_t*)p);
} //}}}

// Worker function. You MUST hold the Play::mutex for handle before calling.
// This function will not return until playback has ceased.
// The mutex will still be locked when it returns.
static void play_terminate_sync(int handle)
{ //{{{
	Play  &play = Play::list[handle];
	pthread_cleanup_push(mutex_cleanup, &play.mutex);
	while( play.state != VPB_AUDIO_IDLE )
	{
		mprintf("play_terminate_sync: waiting for %d, state %d\n",
			handle, play.state);
		play.state = VPB_AUDIO_TERMINATE_SYNC;
		pthread_cond_wait( &play.cond, &play.mutex );
	}
	pthread_cleanup_pop(0);
} //}}}

// Worker function. You MUST hold the Record::mutex for handle before calling.
// This function will not return until recording has ceased.
// The mutex will still be locked when it returns.
static void record_terminate_sync(int handle)
{ //{{{
	Record  &record = Record::list[handle];
	pthread_cleanup_push(mutex_cleanup, &record.mutex);
	while( record.state != VPB_AUDIO_IDLE )
	{
		mprintf("record_terminate_sync: waiting for %d, state %d\n",
			handle, record.state);
		record.state = VPB_AUDIO_TERMINATE_SYNC;
		pthread_cond_wait( &record.cond, &record.mutex );
	}
	pthread_cleanup_pop(0);
} //}}}


void playrec_close()
{ //{{{
	for(size_t i = 0, n = Record::list.size(); i < n; ++i)
	{
		pthread_mutex_lock( &Play::list[i].mutex );
		play_terminate_sync(i);
		pthread_mutex_unlock( &Play::list[i].mutex );

		pthread_mutex_lock( &Record::list[i].mutex );
		record_terminate_sync(i);
		pthread_mutex_unlock( &Record::list[i].mutex );

		if( Record::list[i].v4log_agcstate )
			v4log_agc_close(Record::list[i].v4log_agcstate);
	}

	pthread_attr_destroy( &detached_thread );

	Play::list.clear();
	Record::list.clear();
} //}}}


/*---------------------------------------------------------------------------*\

			       PLAY FUNCTIONS

\*---------------------------------------------------------------------------*/
//{{{

static void play_buf_start(int handle, AudioCompress mode)
{ //{{{
	Play    &play   = Play::list[handle];
	Record  &record = Record::list[handle];

	pthread_mutex_lock( &play.mutex );
	play_terminate_sync(handle);

	// If rgaindown enabled for better Dtmf collection then: Set hw_rec gain override.
	if(record.rgaindown < record.hw_gain)
	{
		mprintf("play_buf_start: Changing RG to [%f]\n", record.rgaindown);

		uint16_t        mess[PC_LCODEC_RECGAIN];
		unsigned short  b,ch;

		maphndletodev(handle, &b, &ch);

		mess[0] = PC_LCODEC_RECGAIN;
		mess[1] = PC_CODEC_RECGAIN;
		mess[2] = ch;
		mess[3] = (uint16_t)(record.rgaindown * -10.0 + 0x80);
		vpb_c->PutMessageVPB(b,mess);
	}
	play.mode  = mode;
	play.state = VPB_AUDIO_PLAYING;
	pthread_mutex_unlock( &play.mutex );
} //}}}

static void play_buf_finish(int handle, bool sync = false)
{ //{{{
	Play           &play   = Play::list[handle];
	Record         &record = Record::list[handle];
	unsigned short  b,ch;

	maphndletodev(handle, &b, &ch);

	pthread_mutex_lock( &play.mutex );

	if(sync) vpb_c->vpbreg(b)->hostdsp->WaitForTxEmpty(ch);
	else     vpb_c->vpbreg(b)->hostdsp->PadTxFrame(ch);

	play.state = VPB_AUDIO_IDLE;

	// If rgaindown enabled for better Dtmf collection then: restore
	if(record.rgaindown < 12)
	{
		mprintf("Restoring RG to [%f]\n", record.hw_gain);

		uint16_t  mess[PC_LCODEC_RECGAIN];

		mess[0] = PC_LCODEC_RECGAIN;
		mess[1] = PC_CODEC_RECGAIN;
		mess[2] = ch;
		mess[3] = (uint16_t)(record.hw_gain * -10.0 + 0x80);
		vpb_c->PutMessageVPB(b,mess);
	}
	pthread_cond_broadcast( &play.cond );
	pthread_mutex_unlock( &play.mutex );
} //}}}

// Multiplies a vector by a gain parameter, result is clamped to fit a short.
static inline void gain_vector(float g, short *v, int n)
{ //{{{
	// This is the maximum scale factor that lets us safely use
	// an int instead of a float as our temporary computed value.
	const unsigned int scalemax = INT_MAX / 32768;

	if( g > scalemax ) g = scalemax;
	for(short *end = v + n; v != end; ++v)
	{
		int tmp = lrintf(*v * g);
		if(__builtin_expect(tmp > 32767,0))       *v = 32767;
		else if(__builtin_expect(tmp < -32768,0)) *v = -32768;
		else                                      *v = tmp;
	}
} //}}}

static int play_buf(int handle,
		    VPBREG *v,
		    unsigned int port,
		    const char *buf,
		    unsigned int length)
{ //{{{
	Play    &play   = Play::list[handle];
	int16_t  wordbuf[v->sztxdf[port]];

//	mprintf("play_buf: playing buffer on channel[%d] board[%d]\n",ch,b);
	while( length && play.state == VPB_AUDIO_PLAYING )
	{
		// wait while DSP fifo is full
		unsigned int words = v->txdf[port]->HowEmpty();

		if( words == 0 ){
			GenericSleep(sleepms);
			continue;
		}

	      #if USE_PLAYREC_DIAG
		// determine how empty buf gets for diagnostic purposes
		int f = v->txdf[port]->HowFull();
		if(f < fplay_min) {
			fplay_min = f;
		}
	      #endif

		// convert buf samples to linear
		unsigned int bytes = (unsigned int)(words*words2bytes[play.mode]);
		if(bytes > length) {
			bytes = length;
			words = (unsigned int)(bytes*bytes2words[play.mode]);
		}

		switch( play.mode )
		{
		    case VPB_LINEAR:
			memcpy(wordbuf, buf, bytes);
			break;

		    case VPB_ALAW:
			alaw_decode(wordbuf, (const unsigned char*)buf, words);
			break;

		    case VPB_MULAW:
			mulaw_decode(wordbuf, (const unsigned char*)buf, words);
			break;

		    case VPB_OKIADPCM:  // not supported yet
		    case VPB_OKIADPCM24:
		    default:
			assert(0);
		}

		// insert software gain
		gain_vector( play.linear_gain, wordbuf, words );

		// alaw compand here to prevent clipping bug
		// XXX ?
		//char     tmp[v->sztxdf[port]];
		//alaw_encode(tmp, (short*)wordbuf, words);
		//alaw_decode((short*)wordbuf, tmp, words);

		length -= bytes;
		buf += bytes;

		// send to DSP tx FIFO
		if( v->txdf[port]->Write((uint16_t*)wordbuf, words) != 0 ){
			assert(0);
		}
	}

	//XXX Racing here, this could return a spurious result.
	return play.state == VPB_AUDIO_PLAYING ? VPB_OK : VPB_FINISH;
} //}}}

static int play_buf_sync(int handle, const char *buf, unsigned short length)
{ //{{{
	unsigned short  b,ch;
	maphndletodev(handle, &b, &ch);

	return play_buf( handle, vpb_c->vpbreg(b), ch, buf, length );
} //}}}

static int play_buf_async(int handle, const char *buf, unsigned short length)
{ //{{{
	unsigned short  b,ch;
	maphndletodev(handle, &b, &ch);

	VPBREG *v = vpb_c->vpbreg(b);

	if( v->txdf[ch]->HowEmpty() < length * bytes2words[Play::list[handle].mode] )
		return -EAGAIN;

	return play_buf( handle, v, ch, buf, length );
} //}}}


// common code for main loop of sync and async play functions.
static void playloop(int handle, size_t bytes, WFILE *wout, FILE *vout, int file_type)
{ //{{{
	Play &play = Play::list[handle];
	char  buf[bytes];
	int   n;

	while( play.state == VPB_AUDIO_PLAYING )
	{
		if(file_type == PLAYREC_WAV)
			n = vpb_wave_read(wout, buf, bytes);
		else
			n = fread(buf, sizeof(char), bytes, vout);

		if( n == 0 ) break;

		play_buf_sync(handle, buf, n);
	}

	if(file_type == PLAYREC_WAV)
		vpb_wave_close_read(wout);
	else
		fclose(vout);
} //}}}

static void play_file_thread_cleanup( void *p )
{ //{{{
	int         handle = (long)p;
	bool        async  = Play::list[handle].state != VPB_AUDIO_TERMINATE_SYNC;
	VPB_EVENT   e;

	play_buf_finish(handle);
	if( async ) {
		e.type   = VPB_PLAYEND;
		e.handle = handle;
		e.data   = 0;
		putevt(&e);
	}
} //}}}

// main loop for async play functions.
static void *play_file_async_thread(void *p)
{ //{{{
	PLAY_FILE_ASYNC_ARGS *args     = (PLAY_FILE_ASYNC_ARGS*)p;
	int		     handle    = args->handle;

	pthread_cleanup_push(play_file_thread_cleanup, (void*)(intptr_t)handle);

	WFILE	             *wout     = args->wout;
	FILE	             *vout     = args->vout;
	size_t	             bytes     = args->bytes;
	int		     data      = args->data;
	int		     file_type = args->file_type;
	VPB_EVENT            e;

	delete args;

	//mprintf("play_file_async_thread starting\n");

	// need to handle exceptions here as we can't throw across threads
	try {
		playloop(handle, bytes, wout, vout, file_type);
		e.data = data;

		mprintf("play_file_async_thread: playloop completed\n");
	}
	catch(const Wobbly &w) {
		mprintf("play_file_async_thread: terminated with exception\n");

		if (file_type == PLAYREC_WAV)
			vpb_wave_close_read(wout);
		else
			fclose(vout);

		e.data = w.code;
	}

	bool    async = Play::list[handle].state != VPB_AUDIO_TERMINATE_SYNC;

	play_buf_finish(handle);

	if( async ) {
		e.type   = VPB_PLAYEND;
		e.handle = handle;
		putevt(&e);
	}

	//mprintf("play_file_async_thread ended normally\n");
	pthread_cleanup_pop(0);

	return NULL;
} //}}}

/*---------------------------------------------------------------------------*\

	FUNCTION: play_file
	AUTHOR..: David Rowe
	DATE....: 20/3/98

	Utility function to play a file to a channel.  This function is used
	to create the async and sync play functions, and handles wave and
	vox file formats.

	int	handle	    handle of destination device
	string	file_name   name of file to play
	int	mode        (only required for PLAYREC_VOX)
	int     file_type   PLAYREC_WAVE || PLAYRECVOX
	int     sync        PLAYREC_SYNC || PLAYREC_ASYNC
	int	data	    VPB_PLAYEND event data (only PLAYREC_ASYNC)

\*--------------------------------------------------------------------------*/

static void play_file(int handle,
		      const string &file_name,
		      AudioCompress mode,
		      int file_type,
		      int sync,
		      int data)
{ //{{{
	WFILE	       *wout = NULL;	// wave file
	FILE	       *vout = NULL;    // vox file
	size_t		bytes;		// num bytes to try to read from file
	unsigned short	words;		// words to download to FIFO
	unsigned short	b,ch;		// board and channel for this handle

	maphndletodev(handle, &b, &ch);
	if (file_type == PLAYREC_WAV) {
		vpb_wave_open_read(&wout, file_name);
		mode = vpb_wave_get_mode(wout);
	} else {
		vout = fopen(file_name.c_str(),"rb");
		if(vout == NULL)
			throw Wobbly(VPBAPI_PLAY_CANT_OPEN_VOXFILE);
	}

	play_buf_start(handle, mode);

	// calculate buffer size to fit DSP FIFO

	words = vpb_c->vpbreg(b)->sztxdf[ch]/2;
	bytes = (size_t)(words*words2bytes[Play::list[handle].mode]);

	// main play loop
	if(sync == PLAYREC_SYNC) {
		// Protect against async cancellation of this thread.
		// XXX Note this might still leak a WFILE object, but since
		//     this should only be needed during app shutdown then
		//     we will tolerate it for now.  This should not be an
		//     issue in later versions.
		pthread_cleanup_push(play_file_thread_cleanup, (void*)(intptr_t)handle);
		playloop(handle, bytes, wout, vout, file_type);
		Play::list[handle].state = VPB_AUDIO_TERMINATE_SYNC;
		pthread_cleanup_pop(1);
		//play_buf_finish(handle);
	}
	else {
		PLAY_FILE_ASYNC_ARGS *args = new PLAY_FILE_ASYNC_ARGS;
		args->handle = handle;
		args->wout = wout;
		args->vout = vout;
		args->bytes = bytes;
		args->data = data;
		args->file_type = file_type;

		pthread_t thread_id;
		pthread_create(&thread_id, &detached_thread,
			       play_file_async_thread, args);
	}
} //}}}

void playrec_new_digit_play(int handle, char digit)
{ //{{{
	Play &play = Play::list[handle];

	pthread_mutex_lock( &play.mutex );

	if( play.state == VPB_AUDIO_PLAYING
	 && play.term_digits.find(digit) != string::npos )
		play.state = VPB_AUDIO_TERMINATE;

	pthread_mutex_unlock( &play.mutex );
} //}}}

void playrec_new_digit_record(int handle, char digit)
{ //{{{
	Record  &record = Record::list[handle];

	pthread_mutex_lock( &record.mutex );

	if( record.state == VPB_AUDIO_RECORDING
	 && record.term_digits.find(digit) != string::npos )
		record.state = VPB_AUDIO_TERMINATE;

	pthread_mutex_unlock( &record.mutex );
} //}}}

void WINAPI vpb_play_set(VPBPortHandle handle, const VPB_PLAY &vpb_play)
{ //{{{
	CheckHandle( handle, "vpb_play_set" );
	validate_digits(vpb_play.term_digits);
	Play::list[handle].term_digits = vpb_play.term_digits;
} //}}}

void WINAPI vpb_play_buf_start(VPBPortHandle handle, AudioCompress mode)
{ //{{{
	CheckHandle( handle, "vpb_play_buf_start" );

	play_buf_start(handle, mode);
} //}}}

int WINAPI vpb_play_buf_sync(VPBPortHandle handle, const char *buf, size_t len)
{ //{{{
	// Don't check the handle here, sane code should have already
	// crapped out in a call to vpb_play_buf_start if it was bad,
	// and this function might be called a lot.

	return play_buf_sync(handle, buf, len);
} //}}}

int WINAPI vpb_play_buf_async(VPBPortHandle handle, const char *buf, size_t len)
{ //{{{
	// Don't check the handle here, sane code should have already
	// crapped out in a call to vpb_play_buf_start if it was bad,
	// and this function might be called a lot.

	return play_buf_async(handle, buf, len);
} //}}}

void WINAPI vpb_play_buf_finish(VPBPortHandle handle)
{ //{{{
	// Don't check the handle here, sane code should have already
	// crapped out in a call to vpb_play_buf_start if it was bad,

	play_buf_finish(handle);
} //}}}

void WINAPI vpb_play_buf_finish_sync(VPBPortHandle handle)
{ //{{{
	// Don't check the handle here, sane code should have already
	// crapped out in a call to vpb_play_buf_start if it was bad,

	play_buf_finish(handle, true);
} //}}}

int WINAPI vpb_play_file_sync(VPBPortHandle handle, const string &file_name)
{ //{{{
	try {
		ValidHandleCheck(handle);
		play_file(handle, file_name, VPB_LINEAR, PLAYREC_WAV, PLAYREC_SYNC, 0);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_play_file_sync");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_play_file_async(VPBPortHandle handle, const string &file_name, int data)
{ //{{{
	try {
		ValidHandleCheck(handle);
		play_file(handle, file_name, VPB_LINEAR, PLAYREC_WAV, PLAYREC_ASYNC, data);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_play_file_async");
	}
	return VPB_OK;
} //}}}

void WINAPI vpb_play_set_gain(VPBPortHandle handle, float gain)
{ //{{{
	CheckHandle(handle, "vpb_play_set_gain");

	Play &play = Play::list[handle];

	play.sw_gain     = gain;
	play.linear_gain = powf(10.0, play.sw_gain / 20.0);
} //}}}

float WINAPI vpb_play_get_gain(VPBPortHandle handle)
{ //{{{
	CheckHandle(handle, "vpb_play_get_gain");

	return Play::list[handle].sw_gain;
} //}}}

void WINAPI vpb_play_set_hw_gain(VPBPortHandle handle, float gain)
{ //{{{
	CheckHandle(handle, "vpb_play_set_hw_gain");

	play_set_hw_gain(handle, gain, vpb_c);
} //}}}

float WINAPI vpb_play_get_hw_gain(VPBPortHandle handle)
{ //{{{
	CheckHandle(handle, "vpb_play_get_hw_gain");

	unsigned short  b,ch;
	maphndletodev(handle, &b, &ch);

	// Always read it from the board, it may have been changed externally.
	return vpb_c->vpbreg(b)->hostdsp->GetHWPlayGain(ch);
} //}}}



/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_play_voxfile_sync
	AUTHOR..: David Rowe & john kostogiannis
	DATE....: 20/8/98

	Function to play a vox file to a channel.  Function returns when
	playing is finished.

	int	handle		handle of destination device
	string	file_name	name of file to play
	int	mode		compression mode

\*--------------------------------------------------------------------------*/

int WINAPI vpb_play_voxfile_sync(int handle, const string &file_name, AudioCompress mode)
{ //{{{
	try {
		ValidHandleCheck(handle);
		play_file(handle, file_name, mode, PLAYREC_VOX, PLAYREC_SYNC,0);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_play_voxfile_sync");
	}
	return VPB_OK;
} //}}}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_play_voxfile_async
	AUTHOR..: David Rowe
	DATE....: 20/3/98

	Utility function to play a vox file to a channel.  Function returns as
	soon as playing has started, and places an event on the API Q when 
	playing has finished.

	int	handle	    handle of destination device
	string	file_name   name of file to play
	int	mode	    compression mode
	int	data	    VPB_PLAYEND event data

\*--------------------------------------------------------------------------*/

int WINAPI vpb_play_voxfile_async(int handle,
				  const string &file_name,
				  AudioCompress mode,
				  int data)
{ //{{{
	try {
		ValidHandleCheck(handle);
		play_file(handle, file_name, mode, PLAYREC_VOX, PLAYREC_ASYNC, data);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_play_voxfile_async");
	}
	return VPB_OK;
} //}}}

AudioState WINAPI vpb_play_state(int handle) { return Play::list[handle].state; }

int WINAPI vpb_play_terminate(int handle)
{ //{{{
	try {
		ValidHandleCheck(handle);

		Play &play = Play::list[handle];

		pthread_mutex_lock( &play.mutex );
		if( play.state == VPB_AUDIO_PLAYING ) play.state = VPB_AUDIO_TERMINATE;
		pthread_mutex_unlock( &play.mutex );
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_play_terminate");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_play_terminate_sync(int handle)
{ //{{{
	try {
		ValidHandleCheck(handle);

		Play  &play = Play::list[handle];
		pthread_mutex_lock( &play.mutex );
		play_terminate_sync(handle);
		pthread_mutex_unlock( &play.mutex );
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_play_terminate");
	}
	return VPB_OK;
} //}}}

//}}}


/*---------------------------------------------------------------------------*\

			     RECORD FUNCTIONS

\*--------------------------------------------------------------------------*/
//{{{

// Returns true if the record session has timed out.
static inline bool time_out(int handle)
{ //{{{
	Record  &record = Record::list[handle];
	return record.time_out
		? (record.time_out + record.time_start) < GenerictimeGetTime()
		: false;
} //}}}

AudioState WINAPI vpb_record_state(int handle) { return Record::list[handle].state; }

static void record_buf_start(int handle, AudioCompress mode)
{ //{{{
	Record  &record = Record::list[handle];

	pthread_mutex_lock( &record.mutex );
	record_terminate_sync(handle);

	record.mode       = mode;
	record.state      = VPB_AUDIO_RECORDING;
	record.time_start = GenerictimeGetTime();
	memset(record.delay, 0, REC_DLY);
	pthread_mutex_unlock( &record.mutex );

      #ifndef CASLOG
	// empty record FIFO
	unsigned short  b,ch;
	maphndletodev(handle, &b, &ch);
	vpb_c->vpbreg(b)->rxdf[ch]->Flush();
      #endif
} //}}}

// Signals the recording of a channel is completed.
static inline void record_buf_finish(int handle)
{ //{{{
	Record  &record = Record::list[handle];

	pthread_mutex_lock( &record.mutex );
	record.state = VPB_AUDIO_IDLE;
	pthread_cond_broadcast( &record.cond );
	pthread_mutex_unlock( &record.mutex );
} //}}}

// internal worker function
static int record_buf(int handle, VPBREG *v, unsigned int port,
		      char *buf, unsigned int length)
{ //{{{
	Record  &record = Record::list[handle];

	// number of samples (words) to upload
	unsigned int    words = v->szrxdf[port]/2;
	int16_t         wordbuf[words];
	int16_t         agcbuf[words];

	while( length && record.state == VPB_AUDIO_RECORDING && ! time_out(handle) )
	{
		// upload half DSP FIFO size buffers at a time,
		// wait for buffer to half fill
		if( v->rxdf[port]->HowFull() < words ){
			GenericSleep(sleepms);
			continue;
		}

	      #if USE_PLAYREC_DIAG
		//mprintf("[%02d/%02d]Got half a buffers worth[%d]...\n",b,ch,f);
		unsigned int f = v->rxdf[port]->HowFull();
		if(f > frec_max)
			frec_max = f;
	      #endif

		// Work out how much to read
		unsigned int bytes = (unsigned int)(words*words2bytes[record.mode]);
		if(bytes > length)  {
			bytes = length;
			words = (unsigned int)(bytes*bytes2words[record.mode]);
		}
		// read it into wordbuf
		if( v->rxdf[port]->Read((uint16_t*)wordbuf, words) != Fifo::OK ){
			mprintf("[%d/%d] ERROR: Reading from FIFO failed\n",
				v->cardnum, port);
			continue;
		}

		// apply AGC for V4LOG card 
		if(record.v4log_agcstate) {
			v4log_agc_apply(record.v4log_agcstate, agcbuf, wordbuf, words);
			memcpy(wordbuf, agcbuf, bytes);
		}

		// insert software gain	  	  	     
		gain_vector(record.linear_gain, wordbuf, words);
		//mprintf("[%02d/%02d] gain[%f]\n",b,ch,record.linear_gain);

		// Delay record samples if we have record on termination digits enabled
		// This ensures DTMF samples are not included in recorded file
		// shouldnt be used for real time recording, e.g. VOIP apps,
		// but in that case DTMF termination of rec is unlikely to be
		// used. 
		if( ! record.term_digits.empty() ) {
			char *delay = record.delay;
			int       n = words * sizeof(short);
			assert(REC_DLY > n);

			//assert(bytes == words*sizeof(short));
			//printf("bytes = %d  words = %d\n", bytes, n);
			memcpy(&delay[REC_DLY-n], wordbuf, n);     // input
			memcpy(wordbuf, delay, n);                 // output
			memmove(delay, &delay[n], REC_DLY-n);      // shift
		}

		switch( record.mode )
		{
		    case VPB_LINEAR:
			memcpy(buf, wordbuf, bytes);
			break;

		    case VPB_ALAW:
			alaw_encode(buf, wordbuf, words);
			break;

		    case VPB_MULAW:
			mulaw_encode(buf, wordbuf, words);
			break;

		    case VPB_OKIADPCM:  // not supported yet
		    case VPB_OKIADPCM24:
		    default:
			assert(0);
		}

		length -= bytes;
		buf += bytes;
	}

	//XXX Racing here, this could return a spurious result.
	return record.state != VPB_AUDIO_RECORDING || time_out(handle)
	       ? VPB_FINISH : VPB_OK;
} //}}}

static int record_buf_sync(int handle, char *buf, unsigned int length)
{ //{{{
	unsigned short	b,ch;

	maphndletodev(handle, &b, &ch);
	return record_buf( handle, vpb_c->vpbreg(b), ch, buf, length );
} //}}}

static int record_buf_async(int handle, char *buf, unsigned int length)
{ //{{{
	unsigned short	b,ch;

	maphndletodev(handle, &b, &ch);
	VPBREG *v = vpb_c->vpbreg(b);

	if( v->rxdf[ch]->HowFull() < length * bytes2words[Record::list[handle].mode] )
		return -EAGAIN;

	return record_buf( handle, v, ch, buf, length );
} //}}}

/*---------------------------------------------------------------------------*\

	FUNCTION: recordloop
	AUTHOR..: David Rowe
	DATE....: 10/3/02

	Helper function containing common code for main loop of sync and async
	record functions.

\*---------------------------------------------------------------------------*/

static void recordloop(int handle, size_t bytes, WFILE *win, FILE *vin, int file_type)
{ //{{{
	char buf[bytes];

	try {
		Record  &record = Record::list[handle];
		while( record.state == VPB_AUDIO_RECORDING && ! time_out(handle) )
		{
			if( record_buf_sync(handle, buf, bytes) != VPB_OK )
				break;

			if (file_type == PLAYREC_WAV)
				vpb_wave_write(win, buf, bytes);
			else
				fwrite(buf, sizeof(char), bytes, vin);
		}

		record_buf_finish(handle);
		if (file_type == PLAYREC_WAV)
			vpb_wave_close_write(win);
		else
			fclose(vin);
	}
	catch(const Wobbly &w) {
		record_buf_finish(handle);
		if (file_type == PLAYREC_WAV)
			vpb_wave_close_write(win);
		else
			fclose(vin);
	}
} //}}}

static void record_file_thread_cleanup( void *p )
{ //{{{
	int         handle = (long)p;
	bool        async  = Record::list[handle].state != VPB_AUDIO_TERMINATE_SYNC;
	VPB_EVENT   e;

	record_buf_finish(handle);
	if( async ) {
		e.type   = VPB_RECORDEND;
		e.handle = handle;
		e.data   = 0;
		putevt(&e);
	}
} //}}}

// main loop for async record functions.
static void *record_file_async_thread(void *p)
{ //{{{
	RECORD_FILE_ASYNC_ARGS *args      = (RECORD_FILE_ASYNC_ARGS*)p;
	int			handle    = args->handle;

	pthread_cleanup_push(record_file_thread_cleanup, (void*)(intptr_t)handle);

	WFILE		       *win       = args->win;
	FILE		       *vin       = args->vin;
	size_t			bytes     = args->bytes;
	int			file_type = args->file_type;
	VPB_EVENT		e;

	delete args;

	// need to handle exceptions here as we can't throw across threads
	try {
		recordloop(handle, bytes, win, vin, file_type);
		//XXX Racing here. This could return a spurious timeout result.
		e.data = time_out(handle) ? VPB_RECORD_TIMEOUT : 0;
	}
	catch(const Wobbly &w) {

		if (file_type == PLAYREC_WAV)
			vpb_wave_close_write(win);
		else
			fclose(vin);

		e.data = w.code;
	}

	bool    async = Record::list[handle].state != VPB_AUDIO_TERMINATE_SYNC;

	record_buf_finish(handle);
	if( async ) {
		e.type   = VPB_RECORDEND;
		e.handle = handle;
		putevt(&e);
	}

	pthread_cleanup_pop(0);

	return NULL;
} //}}}

static void record_file_sync_cleanup( void *p )
{ //{{{
	int         handle = (long)p;
	VPB_EVENT   e;

	record_buf_finish(handle);
	e.type   = VPB_RECORDEND;
	e.handle = handle;
	e.data   = 0;
	putevt(&e);
} //}}}

/*---------------------------------------------------------------------------*\

	FUNCTION: record_file
	AUTHOR..: David Rowe
	DATE....: 20/3/98

	Utility function to record a file from a channel.  This function is 
	used to create the async and sync record functions, and handles wave 
	and vox file formats.  The function continues until the recording is 
	terminated.

	int	handle		handle of source device
	string	file_name	name of file to record
	int 	mode		specifies compression mode
	int	file_type	PLAYREC_WAVE || PLAYRECVOX
	int	sync		PLAYREC_SYNC || PLAYREC_ASYNC

\*--------------------------------------------------------------------------*/

static void record_file(int handle,
			const string &file_name,
			AudioCompress mode,
			int file_type,
			int sync)
{ //{{{
	WFILE		*win = NULL;
	FILE            *vin = NULL;
	size_t		bytes;		// num bytes to try to read from file
	unsigned short	words;		// words to upload from FIFO
	unsigned short	b,ch;		// board and channel for this handle

	maphndletodev(handle, &b, &ch);
	if(file_type == PLAYREC_WAV) {
		vpb_wave_open_write(&win, file_name, mode);
	} else {
		vin = fopen(file_name.c_str(),"wb");
		if (vin == NULL)
			throw Wobbly(VPBAPI_RECORD_CANT_OPEN_VOXFILE);
	}

	record_buf_start(handle, mode);

	// calculate buffer size to fit DSP FIFO

	words = vpb_c->vpbreg(b)->szrxdf[ch]/2;
	bytes = (size_t)(words*words2bytes[Record::list[handle].mode]);

	// main record loop
	if(sync == PLAYREC_SYNC) {
		// Protect against async cancellation of this thread.
		// XXX Note this might still leak a WFILE object, but since
		//     this should only be needed during app shutdown then
		//     we will tolerate it for now.  This should not be an
		//     issue in later versions.
		pthread_cleanup_push(record_file_sync_cleanup, (void*)(intptr_t)handle);
		recordloop(handle, bytes, win, vin, file_type);
		pthread_cleanup_pop(0);
	}
	else {
		RECORD_FILE_ASYNC_ARGS *args = new RECORD_FILE_ASYNC_ARGS;
		args->handle    = handle;
		args->win       = win;
		args->vin       = vin;
		args->bytes     = bytes;
		args->file_type = file_type;

		pthread_t thread_id;
		pthread_create(&thread_id, &detached_thread,
			       record_file_async_thread, args);
	}
} //}}}

void WINAPI vpb_record_set(VPBPortHandle handle, const VPB_RECORD &vpb_record)
{ //{{{
	CheckHandle( handle, "vpb_record_set" );
	validate_digits(vpb_record.term_digits);

	Record  &record = Record::list[handle];

	record.term_digits = vpb_record.term_digits;
	record.time_out = vpb_record.time_out;
} //}}}

void WINAPI vpb_record_buf_start(VPBPortHandle handle, AudioCompress mode)
{ //{{{
	CheckHandle( handle, "vpb_record_buf_start" );

	record_buf_start(handle, mode);
} //}}}

int WINAPI vpb_record_buf_sync(VPBPortHandle handle, char *buf, size_t len)
{ //{{{
	// Don't check the handle here, sane code should have already
	// crapped out in a call to vpb_record_buf_start if it was bad,
	// and this function might be called a lot.

	return record_buf_sync(handle, buf, len);
} //}}}

int WINAPI vpb_record_buf_async(VPBPortHandle handle, char *buf, size_t len)
{ //{{{
	// Don't check the handle here, sane code should have already
	// crapped out in a call to vpb_record_buf_start if it was bad,
	// and this function might be called a lot.

	return record_buf_async(handle, buf, len);
} //}}}

void WINAPI vpb_record_buf_finish(VPBPortHandle handle)
{ //{{{
	// Don't check the handle here, sane code should have already
	// crapped out in a call to vpb_record_buf_start if it was bad,

	record_buf_finish(handle);
} //}}}

int WINAPI vpb_record_file_sync(VPBPortHandle handle,
				const string &file_name,
				AudioCompress mode)
{ //{{{
	try {
		ValidHandleCheck(handle);
		record_file(handle, file_name, mode, PLAYREC_WAV, PLAYREC_SYNC);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_record_file_sync");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_record_file_async(VPBPortHandle handle,
				 const string &file_name,
				 AudioCompress mode)
{ //{{{
	try {
		ValidHandleCheck(handle);
		record_file(handle, file_name, mode, PLAYREC_WAV, PLAYREC_ASYNC);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_record_file_async");
	}
	return VPB_OK;
} //}}}

void WINAPI vpb_record_set_gain(VPBPortHandle handle, float gain)
{ //{{{
	CheckHandle(handle, "vpb_record_set_gain");

	Record  &record = Record::list[handle];

	record.sw_gain     = gain;
	record.linear_gain = powf(10.0, record.sw_gain / 20.0);
} //}}}

float WINAPI vpb_record_get_gain(VPBPortHandle handle)
{ //{{{
	CheckHandle(handle, "vpb_record_get_gain");

	return Record::list[handle].sw_gain;
} //}}}

void WINAPI vpb_record_set_hw_gain(VPBPortHandle handle, float gain)
{ //{{{
	CheckHandle(handle, "vpb_record_set_hw_gain");

	record_set_hw_gain(handle, gain, vpb_c);
} //}}}

float WINAPI vpb_record_get_hw_gain(VPBPortHandle handle)
{ //{{{
	CheckHandle(handle, "vpb_record_get_hw_gain");

	unsigned short  b,ch;
	maphndletodev(handle, &b, &ch);

	// Always read it from the board, it may have been changed externally.
	return vpb_c->vpbreg(b)->hostdsp->GetHWRecordGain(ch);
} //}}}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_record_voxfile_sync
	AUTHOR..: David Rowe & John Kostogiannis
	DATE....: 20/8/98

	Utility function to record a vox file to a channel.  Function returns 
	when recording is finished.

	int	handle		handle of destination device
	string	file_name	name of file to record
	int	mode		specifies compression mode

\*--------------------------------------------------------------------------*/

int WINAPI vpb_record_voxfile_sync(int handle, const string &file_name, AudioCompress mode)
{ //{{{
	try {
		ValidHandleCheck(handle);
		record_file(handle, file_name, mode, PLAYREC_VOX, PLAYREC_SYNC);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_record_voxfile_sync");
	}
	return VPB_OK;
} //}}}

/*---------------------------------------------------------------------------*\

	FUNCTION: vpb_record_voxfile_async
	AUTHOR..: David Rowe
	DATE....: 20/3/98

	Utility function to record a vox file from a channel.  Function 
	returns as soon as recording has started, and places an event on the 
	API Q when recording has finished.

	int	handle		handle of destination device
	string	file_name	name of file to record
	int	mode		specifies compression mode

\*--------------------------------------------------------------------------*/

int WINAPI vpb_record_voxfile_async(int handle, const string &file_name, AudioCompress mode)
{ //{{{
	try {
		ValidHandleCheck(handle);
		record_file(handle, file_name, mode, PLAYREC_VOX, PLAYREC_ASYNC);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_record_voxfile_async");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_record_terminate(int handle)
{ //{{{
	try {
		ValidHandleCheck(handle);

		Record  &record = Record::list[handle];

		pthread_mutex_lock( &record.mutex );
		if( record.state == VPB_AUDIO_RECORDING ) record.state = VPB_AUDIO_TERMINATE;
		pthread_mutex_unlock( &record.mutex );
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_record_terminate");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_record_terminate_sync(int handle)
{ //{{{
	try {
		Record  &record = Record::list[handle];

		ValidHandleCheck(handle);

		pthread_mutex_lock( &record.mutex );
		record_terminate_sync(handle);
		pthread_mutex_unlock( &record.mutex );
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_record_terminate");
	}
	return VPB_OK;
} //}}}

//}}}



//XXX
#if USE_PLAYREC_DIAG
//{{{

//Returns playrec diagnostics.
void playrec_diag(unsigned short *play, unsigned short *rec)
{
	*play = fplay_min;
	*rec = frec_max;
}

// Resets playrec diagnostics.
void playrec_diag_reset()
{
	fplay_min = 64000;
	frec_max = 0;
}

// test functions, DR 12/9/02

void record_get_hw_gain(int handle, float *gain)
{
	*gain = Record::list[handle].hw_gain;
}
void record_get_sw_gain(int handle, float *gain)
{
	*gain = Record::list[handle].sw_gain;
}

void play_get_hw_gain(int handle, float *gain)
{
	*gain = Play::list[handle].hw_gain;
}
void play_get_sw_gain(int handle, float *gain)
{
	*gain = Play::list[handle].sw_gain;
}
//}}}
#endif

