/*
**
**	$Id: bchanneljobs.c,v 1.1 1995/07/13 02:53:15 chris Exp $
**	$Revision: 1.1 $
**
**	$Filename: developer/bchanneljobs.c $
**	$Author: chris $
**	$Date: 1995/07/13 02:53:15 $ 
**	$Portability: ANSI $
**
**	B-Kanal Lese/Schreibjobs
**
**	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF RELOG AG.
**
**	COPYRIGHT (C) 1992-1995 BY RELOG AG, ZUERICH. ALL RIGHTS RESERVED.
**	NO PART OF THIS SOFTWARE MAY BE COPIED, REPRODUCED, OR TRANSMITTED
**	IN ANY FORM OR BY ANY MEANS,  WITHOUT THE PRIOR WRITTEN PERMISSION
**	OF RELOG AG.
**
*/

#include "os.h"
#include "capi-usr.h"

#define _BCHANNELJOBS
#include "bchanneljobs.h"
#include "alaw.h"
#include "am.h"
#include "process.h"


static U8	__far tempbuf8[B_BLOCKSIZE];
static S16	__far tempbuf16[B_BLOCKSIZE];
static S16	__far mixbuf16[B_BLOCKSIZE];


/*
**	Sound mixen, volume = 0..B_VOLUME_MAX
*/
static U16 MixIn( S16 *from, S16 *to, U16 len, U16 volume, U16 endvolume )
{
	while (len)
	{
#if 1
		S32 s = (((S32)*from++ * volume) >> B_VOLUME_SHIFT) + *to;
#else
		S32 s = (S32)*from++ + *to;
#endif

		if (s < -32768)
			s = -32768;

		if (s > 32767)
			s = 32767;

		*to++ = (S16)s;


		/*
		**	Der megacoole Fader
		*/
		if (volume > endvolume)
			volume--;
		else if (volume < endvolume)
			volume++;


		len--;
	}

	return volume;
}


/*
**	Einsprung des File I/O-Prozesses eines B-Kanals
*/
static BOOL __saveds IOProcFunc( struct BChannel *bc )
{
	struct BChannel_Job	*job;
	static				S16 inuse;

	/*
	**	Sicherstellen, dass wir nicht 2x gleichzeitig laufen
	*/
	if (++inuse > 1)
	{
		inuse--;
		return TRUE;
	}


	/*
	**	Alle Jobs abarbeiten
	*/
redo:
	DOLIST( &bc->JobList, job )
	{
		struct Buffer *buf = job->PlayBuffer;

		if (buf->EndOfFile)
			continue;

		if (job->Flags & JOBF_WRITE)				/* File --> B-Kanal */
		{
			if (buf->ActLength == 0)				/* Puffer leer ? */
			{
				U16 rest;

/*				printf( "Reading %ld bytes\n", (U32)buf->TotalLength ); */
				buf->Position  = buf->Data;
				buf->ActLength = (U16)fread( buf->Data, 1, buf->TotalLength, job->File );

				rest = buf->TotalLength - buf->ActLength;
				if (rest && (job->Flags & JOBF_LOOP))
				{
/*					printf( "Rewinding file.\n" ); */
					rewind( job->File );
					buf->ActLength += fread( buf->Data + buf->ActLength, 1, rest, job->File );
				}

				if (buf->ActLength == 0)
					buf->EndOfFile = TRUE;

				goto redo;
			}
		}
		else if (job->Flags & JOBF_READ)			/* B-Kanal --> File */
		{
			if (buf->ActLength == buf->TotalLength)	/* Puffer voll ? */
			{
				buf->Position  = buf->Data;
/*				printf( "Writing file\n" ); */
				fwrite( buf->Data, 1, buf->ActLength, job->File );
				buf->ActLength = 0;

				goto redo;
			}
		}
	}

	inuse--;
	return TRUE;
}


/****** BChannel_New ********************************************************
*
*   NAME
*       BChannel_New -- Create a new B-channel entity
*
*   SYNOPSIS
*       channel = BChannel_New( ncci )
*
*       struct BChannel *BChannel_New( U32 );
*
*   FUNCTION
*
*   INPUTS
*
*   RESULTS
*
*   SEE ALSO
*
****************************************************************************/

struct BChannel *
BChannel_New( U32 NCCI )
{
	struct BChannel *bc;

	if ((bc = OS_Malloc( sizeof( *bc ) )) != NULL)
	{
		bc->NCCI = NCCI;

		NEWLIST( &bc->JobList );

		if (bc->IOProcess = Process_Create( IOProcFunc, bc, "AM-IO", 4 ))
		{
			USES_DISABLE

			bc->Buf1.Position = bc->Buf1.Data;
			bc->Buf2.Position = bc->Buf2.Data;
			bc->Buf1.TotalLength = sizeof( bc->BD1 );
			bc->Buf2.TotalLength = sizeof( bc->BD2 );

			bc->WriteBuffer	= &bc->Buf1;
			bc->PlayBuffer	= &bc->Buf2;

			DISABLE;
			ADDTAIL( &bchannellist, &bc->Node );
			ENABLE;
		}
		else
		{
			printf( "Can't create B-channel I/O process\n" );
			OS_Free( bc );
			bc = NULL;
		}
	}

	return bc;
}


/****** BChannel_Free *******************************************************
*
*   NAME
*       BChannel_Free -- Free a B-channel entity
*
*   SYNOPSIS
*       BChannel_Free( bc )
*
*       VOID BChannel_Free( struct BChannel * );
*
*   FUNCTION
*
*   INPUTS
*
*   RESULTS
*
*   SEE ALSO
*
****************************************************************************/

VOID
BChannel_Free( struct BChannel *bc )
{
	if (bc != NULL)
	{
		USES_DISABLE

		DISABLE;

		while (!ISLISTEMPTY( &bc->JobList))
		{
			BChannel_FreeJob( (struct BChannel_Job *)bc->JobList.Head );
		}

		REMOVE( &bc->Node );

		Process_Delete( bc->IOProcess );
/*		bc->IOProcess = NULL; */

		ENABLE;

		OS_Free( bc );
	}
}


/****** BChannel_Find *******************************************************
*
*   NAME
*       BChannel_Find -- Find a B-channel entity by its NCCI
*
*   SYNOPSIS
*       bc = BChannel_Find( ncci )
*
*       struct BChannel *BChannel_Find( U32 );
*
*   FUNCTION
*
*   INPUTS
*
*   RESULTS
*
*   SEE ALSO
*
****************************************************************************/

struct BChannel *
BChannel_Find( U32 ncci )
{
	struct BChannel *bc, *result = NULL;
	USES_DISABLE

	DISABLE;

	DOLIST( &bchannellist, bc )
	{
		if (bc->NCCI == ncci)
		{
			result = bc;
			break;
		}
	}

	ENABLE;

	return result;
}


/****** BChannel_AddJob *****************************************************
*
*   NAME
*       BChannel_AddJob -- Add a read/write job to a B-channel
*
*   SYNOPSIS
*       bc = BChannel_AddJob( bc, filename, flags, U16 volume, sigproc )
*
*       struct BChannel_Job *BChannel_AddJob( struct BChannel *,
*                                             U8 *, U16, U16, U32 );
*
*   FUNCTION
*
*   INPUTS
*       bc       - B-channel to add job to
*       filename - File to read / write data
*       flags    - JOBF_...
*       volume   - 0..B_VOLUME_MAX
*       sigproc  - Process to signal when the job is terminated
*
*   RESULTS
*
*   SEE ALSO
*
****************************************************************************/

struct BChannel_Job *
BChannel_AddJob( struct BChannel *bc, U8 *filename, U16 flags, U16 volume, U32 sigproc )
{
	if ((bc != NULL) && (filename != NULL))
	{
		struct BChannel_Job *job;

/*		printf( "Adding %s job '%s'\n", (flags & JOBF_READ) ? "READ" : "WRITE", filename ); */

		if ((job = OS_Malloc( sizeof( *job ) )) != NULL)
		{
			job->Flags		= flags;
			job->ActVolume	= volume;
			job->EndVolume	= volume;
			job->SigProc	= sigproc;

			job->Buf1.Position = job->Buf1.Data;
			job->Buf2.Position = job->Buf2.Data;
			job->Buf1.TotalLength = sizeof( job->BD1 );
			job->Buf2.TotalLength = sizeof( job->BD2 );

			job->BChannel		= bc;

			job->WriteBuffer	= &job->Buf1;
			job->PlayBuffer		= &job->Buf2;

			if (job->File = fopen( filename, (flags & JOBF_WRITE) ? "r" : "w" ))
			{
				BOOL firsttime = ISLISTEMPTY( &bc->JobList );
				USES_DISABLE;

				setbuf( job->File, NULL );				/* Unbuffered i/o */

				DISABLE;
				ADDTAIL( &bc->JobList, &job->Node );
				ENABLE;

				IOProcFunc( bc );					/* Daten des neuen Files lesen */

				if (firsttime)
				{
					BChannel_DataB3Conf( bc->NCCI );	/* 2x für Doublebuffering */
					BChannel_DataB3Conf( bc->NCCI );
				}

				return job;
			}
			else
			{
				printf( "Can't open file '%s'\n", filename );
			}

			OS_Free( job );
		}
	}

	return NULL;
}


/****** BChannel_FreeJob ****************************************************
*
*   NAME
*       BChannel_FreeJob -- Free a read/write job
*
*   SYNOPSIS
*       BChannel_FreeJob( bc )
*
*       VOID BChannel_FreeJob( struct BChannel * );
*
*   FUNCTION
*       Removes the job if it's in the job list, and frees its resources.
*
*   INPUTS
*
*   RESULTS
*
*   SEE ALSO
*
****************************************************************************/

VOID
BChannel_FreeJob( struct BChannel_Job *job )
{
	if (job != NULL)
	{
		USES_DISABLE

		DISABLE;
		if (job->Node.Succ != NULL)
		{
			REMOVE( &job->Node );
			job->Node.Succ = NULL;
		}
		ENABLE;

		if (job->File != NULL)
		{
			fclose( job->File );
/*			job->File = NULL; */
		}

		if (job->SigProc)
		{
			Process_WakeUp( job->SigProc );
		}

		OS_Free( job );
	}
}


/****** BChannel_DataB3Ind **************************************************
*
*   NAME
*       BChannel_DataB3Ind -- Handle incoming data
*
*   SYNOPSIS
*       BChannel_DataB3Ind( ncci, data, length )
*
*       VOID BChannel_DataB3Ind( U32, U8 *, U16 );
*
*   FUNCTION
*
*   INPUTS
*
*   RESULTS
*
*   SEE ALSO
*
****************************************************************************/

VOID
BChannel_DataB3Ind( U32 ncci, U8 *data, U16 length )
{
	struct BChannel *bc;

	if (bc = BChannel_Find( ncci ))
	{
		struct BChannel_Job	*job;
		BOOL				work = FALSE;

		DOLIST( &bc->JobList, job )
		{
			if (job->Flags & JOBF_READ)
			{
				struct Buffer	*buf = job->WriteBuffer;
				U16				wlen = buf->TotalLength - buf->ActLength;

				if (wlen > length)
					wlen = length;

				OS_memcpy( buf->Position, data, wlen );
				buf->Position  += wlen;
				data += wlen;
				buf->ActLength += wlen;

				length -= wlen;

				if (buf->ActLength == buf->TotalLength)		/* Puffer voll ? */
				{
					job->WriteBuffer = job->PlayBuffer;		/* Swap */
					job->PlayBuffer  = buf;
					buf = job->WriteBuffer;

					buf->Position  = buf->Data;				/* Neuen Buffer leeren */
					buf->ActLength = 0;

					if (length)
					{
						OS_memcpy( buf->Position, data, length );
						buf->Position  += length;
						buf->ActLength += length;
					}

					work = TRUE;
				}
			}
		}

		if (work)
			Process_WakeUp( bc->IOProcess );
	}
}


/****** BChannel_DataB3Conf *************************************************
*
*   NAME
*       BChannel_DataB3Conf -- Handle acknowledgement of outgoing data
*
*   SYNOPSIS
*       BChannel_DataB3Conf( ncci )
*
*       VOID BChannel_DataB3Conf( U32 );
*
*   FUNCTION
*
*   INPUTS
*
*   RESULTS
*
*   SEE ALSO
*
****************************************************************************/

VOID
BChannel_DataB3Conf( U32 ncci )
{
	struct BChannel *bc;

	if (bc = BChannel_Find( ncci ))
	{
		struct BChannel_Job	*job;
		BOOL				work = FALSE;

		struct
		{
			CAPI_MESSAGE	Msg;
			U8				Para[14];
		} data_b3_req;

		U8 *para = data_b3_req.Para;


		OS_memset( mixbuf16, 0, sizeof( mixbuf16 ) );


		/*
		**	Daten aller Jobs zusammenmischen
		*/

		DOLIST( &bc->JobList, job )
		{
			if (job->State == JOBSTATE_TERMINATED)
				continue;

			if (job->Flags & JOBF_WRITE)
			{
				struct Buffer	*buf = job->WriteBuffer;
				U16				wlen = buf->ActLength;

				if (wlen > B_BLOCKSIZE)
					wlen = B_BLOCKSIZE;

				ALAW_ALawTo16Bit( buf->Position, tempbuf16, (U32)wlen );
				buf->Position  += wlen;
				buf->ActLength -= wlen;

				job->ActVolume = MixIn( tempbuf16, mixbuf16, wlen, job->ActVolume, job->EndVolume );

				if (buf->ActLength == 0)					/* Puffer leer ? */
				{
					job->WriteBuffer = job->PlayBuffer;		/* Swap */
					job->PlayBuffer  = buf;
					buf = job->WriteBuffer;

					if (buf->EndOfFile)						/* Keine Daten mehr */
					{
						if (job->Flags & JOBF_AUTOFREE)
						{
							struct BChannel_Job *oldjob = job;
							job = (struct BChannel_Job *)oldjob->Node.Pred;
							BChannel_FreeJob( oldjob );
						}
						else
						{
							job->State = JOBSTATE_TERMINATED;
						}
					}

					work = TRUE;
				}
			}
		}

		ALAW_16BitToALaw( mixbuf16, bc->WriteBuffer->Data, (U32)bc->WriteBuffer->TotalLength );


		/*
		**	DATA_B3_REQ absenden
		*/
		data_b3_req.Msg.Command		= CAPICMD_DATA_B3;
		data_b3_req.Msg.Subcommand	= CAPISUBCMD_REQ;

		ADD_PARA( para, U32,	bc->NCCI );
		ADD_PARA( para, U8 *,	bc->WriteBuffer->Data );
		ADD_PARA( para, U16,	bc->WriteBuffer->TotalLength );
		ADD_PARA( para, U16,	0x1234 );			/* Data handle */
		ADD_PARA( para, U16,	0 );				/* Flags */

		SendCAPIMessage( &data_b3_req.Msg, para );


		/*
		**	Double buffering
		*/
		{
			struct Buffer *tmp = bc->WriteBuffer;
			bc->WriteBuffer = bc->PlayBuffer;
			bc->PlayBuffer = tmp;
		}

		if (work)
		{
/*			printf( "DataB3Conf: waking up I/O process 0x%08lx\n", bc->IOProcess ); */
			Process_WakeUp( bc->IOProcess );
		}
	}
}

