/*
 * DATlib   Copyright (c) 1995-1996 Marcus Meissner &
 *          Friedrich-Alexander Universitaet Erlangen-Nuernberg (FAU), IMMD4
 *          All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by Marcus Meissner
 *    at the Friedrich-Alexander Universitaet Erlangen-Nuernberg (FAU), IMMD4
 *
 * 4. The name of the University or the author may not be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND THE AUTHOR ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE UNIVERSITY OR THE AUTHOR BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* $Id: dataudiolib.c,v 1.4 1997/08/06 14:59:29 msmeissn Exp $
 *
 * $Log: dataudiolib.c,v $
 * Revision 1.4  1997/08/06 14:59:29  msmeissn
 * reposition on close to make up lost bufferspace
 *
 * Revision 1.3  1997/06/03 18:46:07  msmeissn
 * DA_SET_PACKED badly broken, check for packing/32khz in da_write
 *
 * Revision 1.2  1997/06/03 13:49:11  msmeissn
 * write buffering...
 *
 * Revision 1.1  1997/03/16 20:29:34  msmeissn
 * Initial revision
 *
 *
 * dataudiolib.c: most of the library without the OS-interface
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#ifdef sun
#include <sys/sysmacros.h>      /* minor, used in mtio.h */
#endif
#include <sys/errno.h>
int	errno;
#include <sys/fcntl.h>

#include "dataudiolib.h"
#include "32k_encode.h"
#include "audio_filehdr.h"

/* do not modify these defines */

/* size of blocks we can read from and write to the ARCHIVE Conner DDS drive */
#define FRAMESIZE	5822
#define BLOCKSIZE	(8*FRAMESIZE)
/* maximum datasize */
#define DATASIZE	5760
/* maximum expanded audiodatasize */
#define BUFFERSIZE	(FRAMESIZE*4/3+1000)

/* size of audiodata in 48KHz mode */
#define ASIZE48		DATASIZE
/* ... 44.1KHz mode */
#define ASIZE441	5292
/* ... in 32KHz mode */
#define ASIZE32		3840
/* size of subcode */
#define	SCODESIZE	(7*8+4+2)

/* how many times do we do readpositions to check if the locate is finished 
 * (there is no other way to detect a finished immediate-return locate)
 */
#define DA_LASTLOCSAME	200

/* FIXME: ENDIANESS */

struct da_state	*da_states=NULL;
int		da_entries=0;

#define DA_WRITING(f) (f&(O_RDWR|O_WRONLY))

#define _MIN(a,b) ((a<b)?(a):(b))

void	da_decode_audioblock(struct da_state*);
void	da_decode_subblock(struct da_state* st);
void	da_encode_audioblock(struct da_state*,unsigned char*);
void	da_encode_subblock(struct da_state* st,unsigned char*buf);
void	da_advance_subcode(struct da_state* st);
void	da_advance_time(struct da_time *x,int maxframe);
void	da_check_callbacks(struct da_state *st);
int	cmptime_bl(struct da_time *x,struct da_time *y);
void	da_toc_update(struct da_state*st);
void	da_decode_readpos(struct da_state *st,struct da_position *pos);
void	da_deadvance_time(struct da_time *x,int maxframe);

/* malloc wrapper */
void*	da_alloc(int size) { return malloc(size); }
/* free wrapper */
void	da_free(void *pnt,int size) { free(pnt); }

/* realloc wrapper */
void*
da_realloc(void *oldptr,int size) { 
	if (oldptr==NULL)
		return malloc(size);
	else
		return realloc(oldptr,size);
}

/* devid -> struct da_state lookup helper function */
struct da_state*
da_lookup(int devid) {
	int	i;
	for (i=0;i<da_entries;i++)
		if (da_states[i].id==devid)
			return da_states+i;
	fprintf(stderr,__FILE__":da_lookup:failed to find entry for %d\n",devid);
	return NULL;
}

/* translate channels from subid entry to integer */
int 
da_real_channels(struct da_state *st) { 
	switch (st->subid.channels) {
	case DA_SUBID_CHANNELS_2:
		return 2;
	case DA_SUBID_CHANNELS_4:
		return 4;
	default:
		return 0;
	}
}

/* translate samplerate from subid entry to integer */
int
da_real_samplerate(struct da_state *st) {
	switch (st->subid.samplerate) {
	case DA_SUBID_SAMPLERATE_48KHZ:	return 48000;
	case DA_SUBID_SAMPLERATE_441KHZ:	return 44100;
	case DA_SUBID_SAMPLERATE_32KHZ:	return 32000;
	default: return 0;
	}
}

/* determine audiodatasize from samplerate & encoding */
int
da_determine_size(struct da_state *st,int read_write) {
	switch (st->subid.samplerate) {
	case DA_SUBID_SAMPLERATE_48KHZ:	return DATASIZE;
	case DA_SUBID_SAMPLERATE_441KHZ:	return ASIZE441;
	case DA_SUBID_SAMPLERATE_32KHZ:
		if (st->subid.encoding == DA_SUBID_ENCODING_12) {
			if (read_write)
				return DATASIZE;
			else
				return DATASIZE*4/3;
		} else
			return ASIZE32;
	default:
		fprintf(stderr,__FILE__":bad samplerate %d encountered, assuming %d bytes per frame (44.1 KHz mode)\n",st->subid.samplerate,ASIZE441);
		return ASIZE441;
	}
}

/* close datdevice */
int
da_close(int devid) {
	struct	da_state	*st;
	struct da_locate 	daloc;

	st=da_lookup(devid);
	if (!st) {
		errno=ENOENT;
		return -1;
	}
	/* flush write buffer.
	 * now, let's hope that the user _HAS_ written something
	 */
	if ((DA_WRITING(st->openflags)) && st->readcurlen) {
		int	curleft,res;

		curleft=0;
		while (curleft<st->readcurlen) {
			res=da_downwrite(devid,st->readbuffer+curleft,BLOCKSIZE-curleft);
			if (res==-1) return -1;
			curleft+=res;
		}
	}
	/* We discard the read buffer. it's less than 1/2 second, so the user
	 * shouldn't notice. 
	 * Rewinding the tape for <8 blocks would be too painful anyway
	 *
	 * Well Toerless *DOES* notice *sigh*. So we relocate.
	 */
	if (!(DA_WRITING(st->openflags) && st->readcurlen)) {
		if ((st->values & DA_V_HAVE_PTIME)) {
			daloc.u.time.indexnr = bcd2l(st->progtime.indexnr);
			daloc.u.time.hour = bcd2l(st->progtime.hour);
			daloc.u.time.minute = bcd2l(st->progtime.minute);
			daloc.u.time.second = bcd2l(st->progtime.second);
			daloc.u.time.frame = bcd2l(st->progtime.frame);
			daloc.type = DA_LOC_PROGTIME;
			daloc.nextop = DA_LOC_NEXTOP_READ;
			da_control(devid,DA_LOCATE,&daloc);
		} else if ((st->values & DA_V_HAVE_ATIME)) {
			daloc.u.time.indexnr = bcd2l(st->abstime.indexnr);
			daloc.u.time.hour = bcd2l(st->abstime.hour);
			daloc.u.time.minute = bcd2l(st->abstime.minute);
			daloc.u.time.second = bcd2l(st->abstime.second);
			daloc.u.time.frame = bcd2l(st->abstime.frame);
			daloc.type = DA_LOC_ABSTIME;
			daloc.nextop = DA_LOC_NEXTOP_READ;
			da_control(devid,DA_LOCATE,&daloc);
		}
	}
	return da_downclose(devid);
}

/* add a trackname */
void
da_add_trackname(struct da_state *st,int prognr,char *name,int alreadyintoc) {
	int	i;

	if (st->trackname==NULL)
		st->nroftracknames=0;
	for (i=0;i<st->nroftracknames;i++)
		if (st->trackname[i].prognr==prognr)
			break;
	if (i==st->nroftracknames) {
		st->trackname=da_realloc(st->trackname,(st->nroftracknames+1)*sizeof(struct da_trackname));
		memset(&(st->trackname[i]),'\0',sizeof(struct da_trackname));
		st->nroftracknames++;
		st->trackname[i].name=NULL;
	} else {
		if (	st->trackname[i].name &&
			!strcmp(st->trackname[i].name,name)
		)
			return;
	}
	if (st->trackname[i].name) {
		da_free(st->trackname[i].name,st->trackname[i].namelen);
	}
	st->trackname[i].name=strdup(name);
	st->trackname[i].namelen=strlen(name)+1;
	st->trackname[i].prognr=prognr;
	if (!alreadyintoc) {
		for (i=0;i<st->tocentries;i++) {
			if (st->toc[i].value>=0x100)
				continue;
			if (st->toc[i].prognr!=prognr)
				continue;
			if (st->toc[i].name) {
				da_free(st->toc[i].name,strlen(st->toc[i].name)+1);
				st->toc[i].name=NULL;
			}
			st->toc[i].name=strdup(name);
		}
	}
	st->values|=DA_V_TOC_CHANGED;
	if (st->_track_name) {
		free(st->_track_name);
		st->_track_name=NULL;
	}
	st->_track_curlen=0;
	st->_track_curtrack=0;
}

/* add an entry to the table of contents */
void
da_toc_add(struct da_state *st,struct da_tocentry *te,int flag) {
	int	j,v,p;

	v=te->value;
	p=te->prognr;
	if (st->toc==NULL)
		st->tocentries=0;
	for (j=0;j<st->tocentries;j++) {
		if (v>100)
			if (v==st->toc[j].value)
				break;
			else
				continue;
		if (st->toc[j].value>100)
			continue;
		if (p==st->toc[j].prognr)
			break;
	}

	if (j<st->tocentries) /* found */
		return;
	if (!st->tocentries) {
		st->tocentries	=1;
		st->toc		=(struct da_tocentry*)da_alloc(sizeof(struct da_tocentry));
	} else {
		st->tocentries++;
		st->toc		=(struct da_tocentry*)da_realloc(st->toc,sizeof(struct da_tocentry)*st->tocentries);
	}
	memcpy(st->toc+j,te,sizeof(struct da_tocentry));
	if (st->toc[j].name)
		da_add_trackname(st,st->toc[j].prognr,st->toc[j].name,1);
	if (!flag)
		da_toc_update(st);
}

/* Internal helper function to qsort
 * it defines an internal order between different toc-entries
 */
int
_da_toc_comp(struct da_tocentry *a,struct da_tocentry *b) {
	int	av,bv;

	if (a->value<100) {
		av=a->value+a->prognr*100+4;
	} else {
		switch (a->value) {
		case DA_TOC_AA:av=0;		break; 
		case DA_TOC_BB:av=1;		break;
		case DA_TOC_B0:av=2;		break;
		case DA_TOC_A0:av=3;		break;
		case DA_TOC_A1:av=100005;	break;
		case DA_TOC_C0:av=a->prognr+100006;break;
		case DA_TOC_C1:av=a->prognr+100806;break;
		case DA_TOC_CC:av=1001607;	break; 
		case DA_TOC_EE:av=1001608;	break; 
		default:av=0;
		}
	}
	if (b->value<100) {
		bv=b->value+b->prognr*100+4;
	} else {
		switch (b->value) {
		case DA_TOC_AA:bv=0;		break; 
		case DA_TOC_BB:bv=1;		break;
		case DA_TOC_B0:bv=2;		break;
		case DA_TOC_A0:bv=3;		break;
		case DA_TOC_A1:bv=100005;	break;
		case DA_TOC_C0:bv=b->prognr+100006;break;
		case DA_TOC_C1:bv=b->prognr+100806;break;
		case DA_TOC_CC:bv=1001607;	break; 
		case DA_TOC_EE:bv=1001608;	break; 
		default:bv=0;
		}
	}
	return av-bv;
}

/* sort table of contents, order it, add missing entries. */
void
da_toc_update(struct da_state*st) {
	int	i,j,k,l;
	int	mask;
	struct	da_tocentry te;

	if (st->tocentries==0)
		return;
	k=-1;j=0;l=800;
	for (i=0;i<st->tocentries;i++) {
		if (st->toc[i].value<100) {
			j++;
			if (st->toc[i].prognr<l)
				l=i;
			if (st->toc[i].prognr>k)
				k=i;
		}
	}
	mask=0;
	for (i=0;i<st->tocentries;i++)
		if (st->toc[i].value>=100)
			switch (st->toc[i].value){
			case DA_TOC_A0:mask|=1;break;
			case DA_TOC_A1:mask|=2;break;
			case DA_TOC_B0:
				mask|=4;
				st->toc[i].prognr=j;
				break;
			case DA_TOC_C0:mask|=8;break;
			case DA_TOC_C1:mask|=16;break;
			case DA_TOC_AA:mask|=32;break; 
			case DA_TOC_BB:mask|=64;break; 
			case DA_TOC_CC:mask|=128;break; 
			case DA_TOC_EE:mask|=256;break; 
			}

	mask^=511;
	/* well, is this ok? */
	if (mask!=0) {
		if ((l<800) && (mask&1)) {
			memmove(&te,st->toc+l,sizeof(te));
			te.value=DA_TOC_A0;
			da_toc_add(st,&te,1);
		}
		if ((k>=0) && (mask&2)) {
			/* well, this is DEFINITELY NOT the right way */
			memmove(&te,st->toc+k,sizeof(te));
			te.value=DA_TOC_A1;
			da_toc_add(st,&te,1);
		}
		if (mask&4) {
			memset(&te,'\0',sizeof(te));
			te.value	=DA_TOC_B0;
			te.prognr	=j;
			te.hour		=te.minute=te.second=te.frame=0;
			da_toc_add(st,&te,1);
		}
		/* not quite sure, what C0 and C1 MEAN */
		if (mask&8)	{ }
		if (mask&16)	{ }
		if (mask&32)	{ } /* AA invalid? hmm. */
		if (mask&64)	{ } /* BB programnr tocarea, mark of UTOC */
		if (mask&128)	{ /* CC - TOC will be continued */
			memset(&te,'\0',sizeof(te));
			te.value	=DA_TOC_CC;
			te.prognr	=0x0bb;
			da_toc_add(st,&te,1);
		}
		if (mask&256)	{ } /* endmarker of UTOC */
	}
	qsort((char*)st->toc,st->tocentries,sizeof(struct da_tocentry),
		(int(*)(const void*,const void*))_da_toc_comp);
	st->cnt_toc=0;
}

/* the IOCTL equiv. interface */
int
da_control(int devid,int ctrl,long arg) {
	int	i,j,k;
	struct	da_state	*st;

	st=da_lookup(devid);
	if (st==NULL) {
		errno=EBADF; /* ENXIO? */
		return -1;
	}
/*	fprintf(stderr,"devid=%d,ctrl=%d\n",devid,ctrl);*/
	/* FIXME: any DA_SET_* op should check if we are writing. else they
	 * are rather pointless
	 */
	switch (ctrl) {
/* generate leadin area as defined by the DAT conference standard */
	case DA_WRITE_LEADIN: {
		int	savepacked,savevalues;
		unsigned char	buf[FRAMESIZE];

		if (!DA_WRITING(st->openflags)) {
			errno=EBADF; /* comes closest *shrug* */
			return -1;
		}
		memset(buf,'\0',sizeof(buf));

		savepacked = st->subid.encoding;st->subid.encoding = 0;
		savevalues = st->values;st->values |= DA_V_INSTART;
		st->prognr = 0xAAA;
		/* time is going *DOWN* in the leadin area */
		st->progtime.hour	= 0;
		st->progtime.minute	= 0;
		st->progtime.second	= 10;
		st->progtime.frame	= 0;
		da_encode_subblock(st,buf);

		while (st->progtime.second || st->progtime.frame) {
			int	curleft;

			curleft=0;
			while (curleft<FRAMESIZE) {
				int	res;

				res=da_downwrite(devid,buf+curleft,FRAMESIZE-curleft);
				if(res==-1)
					return -1;
				if (!res) break;
				curleft+=res;
			}
			da_deadvance_time(&(st->progtime),33);
		}
		/* write 30 blocks with time 0 */
		for (i=30;i--;) {
			int	curleft;

			curleft=0;
			while (curleft<FRAMESIZE) {
				int	res;

				res=da_downwrite(devid,buf+curleft,FRAMESIZE-curleft);
				if(res==-1)
					return -1;
				if (!res) break;
				curleft+=res;
			}
		}
		st->prognr = 0x001;
		return 0;
	}
/* generate leadout area as defined by the DAT conference standard */
	case DA_WRITE_LEADOUT:{
		int	savepacked,savevalues;
		unsigned char	buf[FRAMESIZE];

		if (!DA_WRITING(st->openflags)) {
			errno=EBADF; /* comes closest *shrug* */
			return -1;
		}
		memset(buf,'\0',sizeof(buf));

		savepacked = st->subid.encoding;st->subid.encoding=0;
		st->prognr = 0xEEE;
		savevalues = st->values;st->values |= DA_V_INSTART;
		st->progtime.hour	= 0;
		st->progtime.minute	= 0;
		st->progtime.second	= 0;
		st->progtime.frame	= 0;
		da_encode_subblock(st,buf);

		/* 300 +- 30 blocks leadout area.
		 * the subcodes don't change
		 */
		for (i=300;i--;) {
			int	curleft;

			curleft=0;
			while (curleft<FRAMESIZE) {
				int	res;

				res=da_downwrite(devid,buf+curleft,FRAMESIZE-curleft);
				if(res==-1)
					return -1;
				if (!res) break;
				curleft+=res;
			}
		}
		/* restore saved values. */
		st->values = savevalues;
		st->subid.encoding = savepacked;
		return 0;
	}
/* set prerecorded mode flag. doesn't do much yet */
	case DA_SET_PRERECORDED:
		if (!DA_WRITING(st->openflags)) {
			errno=EBADF; /* comes closest *shrug* */
			return -1;
		}
		if (*(long*)arg) 
			st->values |= DA_V_PRERECORDED;
		else
			st->values &= ~DA_V_PRERECORDED;
		break;
/* get prerecorded mode flag */
	case DA_GET_PRERECORDED:
		*(long*)arg=!!(st->values&DA_V_PRERECORDED);
		break;
/* set a tocentry */
	case DA_SET_TOCENTRY: {
		struct da_tocentry	*t=(struct da_tocentry*)arg;
		struct da_tocentry	te;

		if (!DA_WRITING(st->openflags)) {
			errno=EBADF; /* comes closest *shrug* */
			return -1;
		}
		memmove(&te,t,sizeof(te));
		switch (te.value) {
		case DA_TOC_START:
			te.value=0xa0;
			break;
		case DA_TOC_END:
			te.value=0xa1;
			break;
		case DA_TOC_ENTRIES:
			te.value=0xb0;
			break;
		default:te.value=t->value;
		}
		te.prognr=l2bcd(te.prognr);
		te.hour=l2bcd(te.hour);
		te.minute=l2bcd(te.minute);
		te.second=l2bcd(te.second);
		te.frame=l2bcd(te.frame);
		da_toc_add(st,&te,0);
		return 0;
	}
/* get tocentry with specified index and programnr */
	case DA_GET_TOCENTRY: {
		struct da_tocentry	*t=(struct da_tocentry*)arg;
		if (t->value<100) {
			/* get specified entry */
			for (i=0;i<st->tocentries;i++) {
				if (	(st->toc[i].value==t->value) &&
					(st->toc[i].prognr==t->prognr)
				) {
					memmove(t,&(st->toc[i]),sizeof(*t));
					return 0;
				}
			}
			errno=ENOENT;
			return -1;
		}
		switch (t->value) {
		case DA_TOC_START:	
			j=800; /* maximum+1 prognr*/
			k=100; /* maximum+1 index*/
			for (i=0;i<st->tocentries;i++)
				if (st->toc[i].value<100) {
					if (st->toc[i].value<k)
						k=st->toc[i].value;
					if (st->toc[i].prognr<j)
						j=st->toc[i].prognr;
				}
			if (j==800) {
				errno=ENOENT;
				return -1;
			}
			t->value=k;
			t->prognr=j;
			return 0;
		case DA_TOC_END:
			j=0; /* minimum prognr*/
			k=0; /* minimum index*/
			for (i=0;i<st->tocentries;i++)
				if (st->toc[i].value<100) {
					if (st->toc[i].value>k)
						k=st->toc[i].value;
					if (st->toc[i].prognr>j)
						j=st->toc[i].prognr;
				}
			if (j==0) {
				errno=ENOENT;
				return -1;
			}
			t->value=k;
			t->prognr=j;
			return 0;
		case DA_TOC_ENTRIES:
			j=0;
			for (i=0;i<st->tocentries;i++)
				if (st->toc[i].value<100)
					j++;
			t->prognr=j;
			return 0;
		default:
			errno=EINVAL;
			return -1;
		}
		/*NOTREACHED*/
	}
/* get current programnr */
	case DA_GET_PROGNR:
		*(int*)arg=bcd2l(st->prognr);
		return 0;
/* set current programnr */
	case DA_SET_PROGNR:
		if (!DA_WRITING(st->openflags)) {
			errno=EBADF; /* comes closest *shrug* */
			return -1;
		}
		if (st->prognr != l2bcd(*(int*)arg))
			st->values &= ~(DA_V_TRACKTIME|DA_V_TRACKNAME);
		st->prognr	=l2bcd(*(int*)arg);
		st->values	|=DA_V_INSTART;
		st->cnt_start	=300;
		return 0;
/* get program time */
	case DA_GET_PROGTIME:{
		struct	da_time	*t=(struct da_time*)arg;

		if (st->values & DA_V_HAVE_PTIME) {
			t->indexnr=bcd2l(st->progtime.indexnr);
			t->hour=bcd2l(st->progtime.hour);
			t->minute=bcd2l(st->progtime.minute);
			t->second=bcd2l(st->progtime.second);
			t->frame=bcd2l(st->progtime.frame);
			return 0;
		} else {
			errno=ENOENT;
			return -1;
		}
	}
/* get absolute time */
	case DA_GET_ABSTIME:{
		struct	da_time	*t=(struct da_time*)arg;

		if (st->values & DA_V_HAVE_ATIME) {
			t->indexnr=bcd2l(st->abstime.indexnr);
			t->hour=bcd2l(st->abstime.hour);
			t->minute=bcd2l(st->abstime.minute);
			t->second=bcd2l(st->abstime.second);
			t->frame=bcd2l(st->abstime.frame);
			return 0;
		} else {
			errno=ENOENT;
			return -1;
		}
	}
/* get record time */
	case DA_GET_RUNTIME:{
		struct	da_time	*t=(struct da_time*)arg;

		t->indexnr=bcd2l(st->runtime.indexnr);
		t->hour=bcd2l(st->runtime.hour);
		t->minute=bcd2l(st->runtime.minute);
		t->second=bcd2l(st->runtime.second);
		t->frame=bcd2l(st->runtime.frame);
		return 0;
	}
/* set record time */
	case DA_SET_RUNTIME:{
		struct	da_time	*t=(struct da_time*)arg;

		st->runtime.indexnr=l2bcd(t->indexnr);
		st->runtime.hour=l2bcd(t->hour);
		st->runtime.minute=l2bcd(t->minute);
		st->runtime.second=l2bcd(t->second);
		st->runtime.frame=l2bcd(t->frame);
		return 0;
	}
/* set absolute time */
	case DA_SET_ABSTIME:{
		struct	da_time	*t=(struct da_time*)arg;

		st->abstime.indexnr=l2bcd(t->indexnr);
		st->abstime.hour=l2bcd(t->hour);
		st->abstime.minute=l2bcd(t->minute);
		st->abstime.second=l2bcd(t->second);
		st->abstime.frame=l2bcd(t->frame);
		return 0;
	}
/* set programtime */
	case DA_SET_PROGTIME:{
		struct	da_time	*t=(struct da_time*)arg;

		st->progtime.indexnr=l2bcd(t->indexnr);
		st->progtime.hour=l2bcd(t->hour);
		st->progtime.minute=l2bcd(t->minute);
		st->progtime.second=l2bcd(t->second);
		st->progtime.frame=l2bcd(t->frame);
		return 0;
	}
/* get recorddate */
	case DA_GET_RECORDDATE:{
		struct	da_date	*d=(struct da_date*)arg;

		d->weekday=bcd2l(st->recorddate.weekday);
		d->year=bcd2l(st->recorddate.year);
		d->month=bcd2l(st->recorddate.month);
		d->day=bcd2l(st->recorddate.day);
		d->hour=bcd2l(st->recorddate.hour);
		d->minute=bcd2l(st->recorddate.minute);
		d->second=bcd2l(st->recorddate.second);
		st->values |= DA_V_HAVE_RDATE;
		return 0;
	}
/* set recorddate */
	case DA_SET_RECORDDATE:{
		struct	da_date *d=(struct da_date*)arg;

		st->recorddate.weekday=l2bcd(d->weekday);
		st->recorddate.year=l2bcd(d->year);
		st->recorddate.month=l2bcd(d->month);
		st->recorddate.day=l2bcd(d->day);
		st->recorddate.hour=l2bcd(d->hour);
		st->recorddate.minute=l2bcd(d->minute);
		st->recorddate.second=l2bcd(d->second);
		return 0;
	}
/* abort operation , for aborting LOCATEs */
	case DA_ABORT: {
		int	res;

		if (!(st->values & DA_V_IN_LOCATE))
			return 0;
		st->values &= ~DA_V_IN_LOCATE;
		res=da_downcontrol(devid,DA_ABORT,arg);
		if (res==-1)
			return -1;
		return res;
	}
/* rewind the tape */
	case DA_REWIND:
		return 0;
/* unload the tape */
	case DA_EJECT:
		return da_downcontrol(devid,ctrl,arg);
		break;
/* read current tapeposition (for instance while locating) */
	case DA_READ_POSITION: {
		struct	da_position	*dapos;

		if (-1==da_downcontrol(devid,DA_READ_POSITION,arg)) {
			perror("da_downcontrol DA_READ_POSITION");
			return -1;
		}
		dapos=(struct da_position*)arg;
		dapos->prognr=bcd2l(dapos->prognr);
		dapos->indexnr=bcd2l(dapos->indexnr);
		dapos->progtime.indexnr=bcd2l(dapos->progtime.indexnr);
		dapos->progtime.hour=bcd2l(dapos->progtime.hour);
		dapos->progtime.minute=bcd2l(dapos->progtime.minute);
		dapos->progtime.second=bcd2l(dapos->progtime.second);
		dapos->progtime.frame=bcd2l(dapos->progtime.frame);
		dapos->abstime.indexnr=bcd2l(dapos->abstime.indexnr);
		dapos->abstime.hour=bcd2l(dapos->abstime.hour);
		dapos->abstime.minute=bcd2l(dapos->abstime.minute);
		dapos->abstime.second=bcd2l(dapos->abstime.second);
		dapos->abstime.frame=bcd2l(dapos->abstime.frame);
		dapos->runtime.indexnr=bcd2l(dapos->runtime.indexnr);
		dapos->runtime.hour=bcd2l(dapos->runtime.hour);
		dapos->runtime.minute=bcd2l(dapos->runtime.minute);
		dapos->runtime.second=bcd2l(dapos->runtime.second);
		dapos->runtime.frame=bcd2l(dapos->runtime.frame);
		return 0;
	}
/* the locate operation */
	case DA_LOCATE: {
		unsigned char		buf[5822];
		struct	da_position	dapos;
		struct	da_locate	*darealloc;

		st->writecurlen	= 0;
		st->readcurlen	= 0;
		/* if we are already locating, send an abort first */
		if (st->values & DA_V_IN_LOCATE)
			da_control(devid,DA_ABORT,arg);
		/* FIXME: perhaps we should sleep(1); to avoid race conditions ?
		 */
		/* send locate */
		if (-1==da_downcontrol(devid,DA_LOCATE,arg)) 
			return -1;
		/* now read with readpos... */
		st->values	|= DA_V_IN_LOCATE;
		st->lastlocsame	 = DA_LASTLOCSAME;
		st->writecurlen	 = 0;

		if (-1==da_downcontrol(devid,DA_READ_POSITION,(long)&(st->lastreadpos)))
			return -1;
		da_decode_readpos(st,&(st->lastreadpos));
		/* the drive will automatically get the right block for us */
		if (((struct da_locate*)arg)->nextop==DA_LOC_NEXTOP_READ)
			return 0;
		/* now for the *HARD* part, writing */
		/* the tape is several frames away from our target */

		darealloc=(struct da_locate*)arg;

		/* good. just read the current position */
		/* use da_control, for it converts to useful realints :) */

		if (-1==da_control(devid,DA_READ_POSITION,(int)(&dapos))) {
			perror("da_downcontrol DA_READ_POSITION");
			return -1;
		}
		/* this forwards us to the position. */
		if (-1==da_downread(devid,buf,5822)) {
			perror("da_downread in da_control DA_LOCATE");
			return -1;
		}
		return 0;
	}
/* get the number of channels in use */
	case DA_GET_CHANNELS:
		*(long*)arg = da_real_channels(st);
		return 0;
/* set the number of channels */
	case DA_SET_CHANNELS: {
		switch (*(long*)arg) {
		case 2:	st->subid.channels = DA_SUBID_CHANNELS_2;
			return 0;
		case 4:	st->subid.channels = DA_SUBID_CHANNELS_4;
			return 0;
		default:errno = EINVAL;
			return -1;
		}
	}
/* get current sampling rate */
	case DA_GET_SAMPLERATE:
		*(long*)arg=da_real_samplerate(st);
		return 0;
/* set current sampling rate */
	case DA_SET_SAMPLERATE: {
		switch (*(long*)arg) {
		case 44100:
			st->subid.samplerate = DA_SUBID_SAMPLERATE_441KHZ;
			return 0;
		case 48000:
			st->subid.samplerate = DA_SUBID_SAMPLERATE_48KHZ;
			return 0;
		case 32000:
			st->subid.samplerate = DA_SUBID_SAMPLERATE_32KHZ;
			return 0;
		default:
			errno=EINVAL;
			return -1;
		}
	}
/* set encoding , 0 - 16 bit, 1 - 12 bit */
	case DA_SET_PACKED: {
		if (*(long*)arg)
			st->subid.encoding=DA_SUBID_ENCODING_12;
		else
			st->subid.encoding=DA_SUBID_ENCODING_16;
		return 0;
	}
/* get encoding */
	case DA_GET_PACKED:
		*(long*)arg=st->subid.encoding == DA_SUBID_ENCODING_12;
		return 0;
/* get current ISRC */
	case DA_GET_ISRC:
		memmove((caddr_t)arg,&(st->isrc),sizeof(st->isrc));
		return 0;
/* set current ISRC */
	case DA_SET_ISRC:
		memmove(&(st->isrc),(caddr_t)arg,sizeof(st->isrc));
		return 0;
/* get current catalog nr */
	case DA_GET_CATALOG:
		memmove((caddr_t)arg,&(st->catalog),sizeof(st->catalog));
		return 0;
/* set current catalog nr */
	case DA_SET_CATALOG:
		memmove(&(st->catalog),(caddr_t)arg,sizeof(st->catalog));
		st->values|= DA_V_HAVE_CATALOG;
		return 0;
/* set current trackname */
	case DA_SET_TRACKNAME:
		if (st->curtrackname)
			free(st->curtrackname);
		st->curtrackname=strdup((char*)arg);
		st->values |= DA_V_TRACKNAME;
		return 0;
/* get current trackname */
	case DA_GET_TRACKNAME:
		if (!(st->values & DA_V_TRACKNAME)) {
			*(char*)arg='\0';
			return 0;
		}
		if (!st->curtrackname)
			*(char*)arg='\0';
		else
			strcpy((char*)arg,st->curtrackname);
		return 0;
/* set current tracktime */
	case DA_SET_TRACKTIME:
		memcpy(&(st->curtracktime),(char*)arg,sizeof(struct da_time));
		st->values |= DA_V_TRACKTIME;
		return 0;
/* get current tracktime */
	case DA_GET_TRACKTIME:
		if (st->values & DA_V_TRACKTIME)
			memcpy((char*)arg,&(st->curtracktime),sizeof(struct da_time));
		else {
			errno=ENOENT;
			return -1;
		}
		return 0;
	default:break;
	}
	errno=ENOSYS;
	return -1;
}

static int da_hostendianess=0;

int
da_interninit(int id,int flags,char *fn) {
	struct	da_state	xst,*st;
	int			i;

	if (!da_hostendianess) {
		long a=0x12345678;char *b;

		b=(char*)&a;
		if (b[0]==0x12)
			da_hostendianess=1234;
		else
			da_hostendianess=4321;
	}
	if (!da_c_44_to_32lp[2])
		da_init_tables();
	memset(&xst,'\0',sizeof(xst));
	xst.values=0;
	xst.toc	=NULL;
	xst.tocentries	=0;
	id = da_downinit(id,flags,fn,&xst);
	if (id==-1) return -1;
	for (i=0;i<da_entries;i++)
		if (id==da_states[i].id)
			break;
	st=da_states+i;
	if (i==da_entries) {
		if (da_states==NULL) {
			da_states=(struct da_state*)da_alloc(sizeof(struct da_state));
			da_entries++;
			st=da_states+i;
		} else {
			st=(struct da_state*)da_alloc((++da_entries)*sizeof(struct da_state));
			memmove(st,da_states,sizeof(struct da_state)*(da_entries-1));
			da_free(da_states,sizeof(struct da_state)*(da_entries-1));
			da_states=st;
			st=da_states+i;
		}
		memmove(st,&xst,sizeof(xst));
		if (!(st->values&DA_V_STATE_LOADED)) {
			st->subid.samplerate    = DA_SUBID_SAMPLERATE_48KHZ;
			st->subid.channels = DA_SUBID_CHANNELS_2;
			st->subid.encoding = DA_SUBID_ENCODING_16;
			st->values=0;
			st->bnr=0;
			/* 
			 * Idea:emulate writing of leadin area... 
			 * 	but this could do harm if we restart the code...
			 */
			st->tocentries		=0;
			st->toc			=NULL;
			st->abstime.indexnr	=
			st->abstime.hour	=
			st->abstime.minute	=
			st->abstime.second	=
			st->abstime.frame	=0;
			/* no need to set abstime in values */
			st->prognr=1;
			st->cnt_toc=0;
			st->cnt_start=300; /* well ,we start here, right? */
			st->values|=DA_V_INSTART;
			st->cnt_jump=0;
		} else {
			st->values &= ~(DA_V_IN_LOCATE|DA_V_TRACKTIME|DA_V_TRACKNAME);
			/* on load? what do we have to fix ? */
		}
		st->toccurrent=0;
		st->toctowrite=0;
		st->id=id;
	}
	/* allocate maximum size for datablock+maximum header size */
	st->devname=fn;
	st->writebuffer=(unsigned char*)da_alloc(BUFFERSIZE);
	st->cb			= NULL;
	st->writecurlen		= 0;
	st->openflags		= flags;
	st->readbuffer		= (u8*)malloc(BLOCKSIZE);
	st->readcurlen		= 0;
	if (!(st->values&DA_V_STATE_LOADED))
		st->values&=~DA_V_READ_OR_WRITTEN;
	return id;
}

int
da_init(int fd,int flags) {
	char	*fn;

	fn=(char*)malloc(10);
	sprintf(fn,"%d",fd);
	return da_interninit(fd,flags,fn);
}

/* device open routine */
int
da_open(char *devname,int flags) {
	int			fd;

	fd=da_downopen(devname,flags);
	return da_interninit(fd,flags,devname);
}

#define RETURN(x) return (x<0?(errno=-x,-1):x)
/* read frontend function, just like read(2) */
int
da_read(int devid,unsigned char	*buffer,int size) {
	int			len,res,curleft;
	struct	da_state	*st;
	int			cursize;
	struct	da_position	readpos;

	st=da_lookup(devid);
	if (!st) RETURN(-ENOENT);
	if (st->values & DA_V_IN_LOCATE) {
		if (-1==da_downcontrol(devid,DA_READ_POSITION,(long)&readpos))
			return -1;
		if (!memcmp(&readpos,&(st->lastreadpos),sizeof(readpos))) {
			if (--st->lastlocsame<=0)
				st->values &= ~DA_V_IN_LOCATE;
			else
				return 0;
		} else {
			memcpy(&(st->lastreadpos),&readpos,sizeof(readpos));
			st->lastlocsame = DA_LASTLOCSAME;
			da_decode_readpos(st,&(readpos));
			return 0; /* nothing read, perhaps EWOULDBLOCK,-1? */
		}
	}
	cursize=size;
	while (cursize) {
		/* the modes should be one interface layer UP 
		 * in if_userlevel.c 
		 */
		if (!(st->values & DA_V_READ_OR_WRITTEN)) {
			curleft=FRAMESIZE;
			while (curleft) {
				res=da_downread(devid,st->readbuffer+st->readcurlen,curleft);
				if (res==-1) return -1;
				st->readcurlen+=res;
				curleft-=res;
			}
			st->bnr++;
			da_decode_subblock(st);
		}
		/* this could loop for a while (in Jump zones fi) */
		while (st->writecurlen < BUFFERSIZE-FRAMESIZE*4/3) {
			if (st->readcurlen<FRAMESIZE) {
				curleft=BLOCKSIZE;
				while (curleft) {
					res=da_downread(devid,st->readbuffer+st->readcurlen,curleft);
					if (res==-1) {
						if (curleft<BLOCKSIZE)
							break;
						else
							return -1;
					}
					st->readcurlen+=res;
					curleft-=res;
				}
			}
			/* IF LASTPROGNO!=CURRENTPROGNO break; */
			/* this may take a long time ... */
			st->bnr++;
			da_decode_subblock(st);
			da_decode_audioblock(st);
		}
		len=_MIN(st->writecurlen,cursize);
		memmove(buffer+(size-cursize),st->writebuffer,len);
		st->writecurlen-=len;
		cursize-=len;
		memmove(st->writebuffer,st->writebuffer+len,st->writecurlen);
	}
	st->values|=DA_V_READ_OR_WRITTEN;
	return size;
}

/* write frontend function, just like write(2) */
int
da_write(int devid,unsigned char	*buffer,int size) {
	int			len,blockread,res,curleft;
	struct	da_state	*st;
	unsigned char			buf[FRAMESIZE];
	int			cursize;

	st=da_lookup(devid);
	if (!st) RETURN(-ENOENT);
	if (	(st->subid.encoding==DA_SUBID_ENCODING_12) &&
		(st->subid.samplerate!=DA_SUBID_SAMPLERATE_32KHZ)
	)
		RETURN(EINVAL);

	/* if (!st->recorddate.weekday) */{

		struct	tm	*xtm;
		time_t		t;

		t=time(NULL);
		xtm=localtime(&t);
		st->recorddate.weekday	= l2bcd(xtm->tm_wday+1);
		st->recorddate.day	= l2bcd(xtm->tm_mday);
		st->recorddate.month	= l2bcd(xtm->tm_mon);
		st->recorddate.hour	= l2bcd(xtm->tm_hour);
		st->recorddate.minute	= l2bcd(xtm->tm_min);
		st->recorddate.second	= l2bcd(xtm->tm_sec);
		st->recorddate.year	= l2bcd(xtm->tm_year);
	}
	blockread=0;cursize=size;
	while (cursize) {
		len=_MIN(cursize,BUFFERSIZE-st->writecurlen);
		memmove(st->writebuffer+st->writecurlen,buffer+(size-cursize),len);
		cursize-=len;
		st->writecurlen+=len;
		while (st->writecurlen>=da_determine_size(st,0)) {
			st->bnr++;
			da_encode_audioblock(st,buf);
			da_encode_subblock(st,buf);
			da_advance_subcode(st);
			/* Write buffering. won't wrap at the edge, 
			 * for BLOCKSIZE is a multidingens of FRAMESIZE 
			 * and always started at 0.
			 */
			memcpy(st->readbuffer+st->readcurlen,buf,FRAMESIZE);
			st->readcurlen+=FRAMESIZE;
			if (st->readcurlen<BLOCKSIZE)
				continue;
			curleft=0;
			while (curleft<BLOCKSIZE) {
				res=da_downwrite(devid,st->readbuffer+curleft,BLOCKSIZE-curleft);
				if (res==-1) return -1;
				curleft+=res;
			}
			st->readcurlen=0;
		}
	}
	st->values|=DA_V_READ_OR_WRITTEN;
	return size;
}

/* add event callback */
int
da_up_add_callback(int devid,int id,enum da_cb_type type,enum da_cb_granularity gran) {
	struct	da_state	*st;
	struct	da_callback	*cb;

	st=da_lookup(devid);
	if (!st) RETURN(-ENOENT);
	cb=(struct da_callback*)da_alloc(sizeof(struct da_callback));
	cb->next	= st->cb;
	st->cb		= cb;
	cb->type	= type;
	cb->granularity	= gran;
	cb->id		= id;
	cb->lasttimevalid= 0;
	memset(&(cb->data),'\0',sizeof(cb->data));
	return 0;
}

/* internal audioblock decoding helper function */
void
da_decode_audioblock(struct da_state *st) {
	int	len,dumplen;
	int	i;
	short	*outbuf=(short*)((char*)st->writebuffer+st->writecurlen);
	unsigned char	*obuf=st->writebuffer+st->writecurlen;
	unsigned char	*inbuf=st->readbuffer;

	if (st->readcurlen<FRAMESIZE) {
		fprintf(stderr,"da_decode_audioblock: not enough data to process. Aborting\n");
		exit(1);
	}
	switch (st->subid.samplerate) {
	case DA_SUBID_SAMPLERATE_48KHZ:
		len=dumplen=DATASIZE;
		break;
	case DA_SUBID_SAMPLERATE_441KHZ:
		len=dumplen=ASIZE441;
		break;
	case DA_SUBID_SAMPLERATE_32KHZ:
		/* two modes, two lengths */
		if (st->subid.encoding==DA_SUBID_ENCODING_16) {
			dumplen=len=ASIZE32;
		} else {
			len=DATASIZE;
			dumplen=len*4/3;
		}
		break;
	default:st->readcurlen-=FRAMESIZE;
		memmove(st->readbuffer,st->readbuffer+FRAMESIZE,st->readcurlen);
		fprintf(stderr,"Unknown decoding method, skipping.\n");
		return;
	}
	if (st->subid.encoding==DA_SUBID_ENCODING_12) {
		short	left,right;
		for (i=0;i<len;i+=3) {
			left=da_conv_12_to_16[
				(inbuf[da_c_44_to_32lp[i]]<<4)+
				((inbuf[da_c_44_to_32lp[i+1]]&0xf0)>>4)
			];
			right=da_conv_12_to_16[
				(inbuf[da_c_44_to_32lp[i+2]]<<4)+
				(inbuf[da_c_44_to_32lp[i+1]]&0x0f)
			];
			*outbuf++=left;
			*outbuf++=right;

		}
	} else {
		/* this one is easy. just swap for endianess */

		if (da_hostendianess==4321)/* little endian */
			memcpy(obuf,inbuf,len);
		else {
			for (i=0;i<len;i++)
				obuf[i]=inbuf[i^1];
		}
	}
	st->readcurlen-=FRAMESIZE;
	memmove(st->readbuffer,st->readbuffer+FRAMESIZE,st->readcurlen);
	st->writecurlen+=dumplen;
}

/* internal audioblock encoding helper function */
void
da_encode_audioblock(struct da_state *st,unsigned char *outbuf) {
	int	len;
	int	i,j;
	short	*inbuf=(short*)((char*)st->writebuffer);
	unsigned char	*ibuf=st->writebuffer;

	switch (st->subid.samplerate) {
	case DA_SUBID_SAMPLERATE_48KHZ:
		len=DATASIZE;
		break;
	case DA_SUBID_SAMPLERATE_441KHZ:
		len=ASIZE441;
		break;
	case DA_SUBID_SAMPLERATE_32KHZ:
		/* two modes, two lengths */
		if (st->subid.encoding==DA_SUBID_ENCODING_12)
			len=DATASIZE*4/3;
		else
			len=ASIZE32;
		break;
	default:return;
	}
	if (st->subid.encoding==DA_SUBID_ENCODING_12) {
		if (!da_c_44_to_32lp[2]) {
			da_init_tables();
		}
		j=0;
		for (i=0;i<len/2;i+=2) {
			int left,right;
			left    =da_conv_16_to_12[*(unsigned short*)(inbuf+i)];
			right   =da_conv_16_to_12[*(unsigned short*)(inbuf+i+1)];
			outbuf[da_c_44_to_32lp[j++]]=left>>4;
			outbuf[da_c_44_to_32lp[j++]]=((left&0xf)<<4)|(right&0xf);
			outbuf[da_c_44_to_32lp[j++]]=right>>4;
/*
	writebuf[c_44_to_32lp[j++]]=left/16;
	writebuf[c_44_to_32lp[j++]]=((left&0xf)<<4)|(right&0xf);
	writebuf[c_44_to_32lp[j++]]=right/16;
*/
												 
		}
	} else {
		/* TAPE endianess is Little Endian , so swap, if 
		 * host endianess is Big Endian
		 */
		if (da_hostendianess==4321)/* little endian */
			memcpy(outbuf,ibuf,len);
		else {
			for (i=0;i<len;i++)
				outbuf[i]=ibuf[i^1];
		}
	}
	st->writecurlen-=len;
	memmove(st->writebuffer,st->writebuffer+len,st->writecurlen);
}

/* internal readposition decoding helper function */
void
da_decode_readpos(struct da_state *st,struct da_position *pos) {
	memcpy(&(st->progtime),&(pos->progtime),sizeof(pos->progtime));
	memcpy(&(st->abstime),&(pos->abstime),sizeof(pos->abstime));
	memcpy(&(st->runtime),&(pos->runtime),sizeof(pos->runtime));
	st->prognr		= pos->prognr;
	st->progtime.indexnr	= pos->indexnr;
	st->abstime.indexnr	= pos->indexnr;
	st->runtime.indexnr	= pos->indexnr;
	da_check_callbacks(st);
}

#define MASK(byte,bit) ((mainid[byte]>>bit)&0x3)
#define _CMASK(x) (subid[0]&(1<<x))
/* internal subcode/subid/mainid decoding helper function */
void
da_decode_subblock(struct da_state *st) {
	unsigned char	*buf=st->readbuffer;
	unsigned char	*scode=buf+DATASIZE;
	unsigned char	*subid=scode+7*8;
	unsigned char	*mainid=subid+4;
	int		i;

	st->mute=0;
	/* dataid==0000? if not we don't have audio */
	if ((subid[0]&0xf)!=0)
		st->mute=1;
	st->values	&= ~(DA_V_HAVE_RDATE|DA_V_HAVE_PTIME|DA_V_HAVE_ATIME|DA_V_HAVE_RTIME);
	/* decode the subcode */
	for (i=0;i<7;i++) {
		scode=buf+DATASIZE+i*8;
		/*FIXME should check for checksums and reject if bad */
		switch ((scode[0]&0xf0)>>4) {
		case 1: /* Program time */
			st->progtime.indexnr=scode[2];
			st->progtime.hour=scode[3];
			st->progtime.minute=scode[4];
			st->progtime.second=scode[5];
			st->progtime.frame=scode[6];
			st->values |= DA_V_HAVE_PTIME;
			break;
		case 2:	/* Absolute time */
			st->abstime.indexnr=scode[2];
			st->abstime.hour=scode[3];
			st->abstime.minute=scode[4];
			st->abstime.second=scode[5];
			st->abstime.frame=scode[6];
			st->values |= DA_V_HAVE_ATIME;
			break;
		case 3:	/* Record time */
			st->runtime.indexnr=scode[2];
			st->runtime.hour=scode[3];
			st->runtime.minute=scode[4];
			st->runtime.second=scode[5];
			st->runtime.frame=scode[6];
			st->values |= DA_V_HAVE_RTIME;
			break;
		case 4:	/* TOC entry */ {
			struct	da_tocentry	te;
			memset(&te,'\0',sizeof(te));

			te.value	= scode[2];
			te.prognr	= (scode[0]&0x7)*256+scode[1];
			te.hour		= scode[3];
			te.minute	= scode[4];
			te.second	= scode[5];
			te.frame	= scode[6];
			da_toc_add(st,&te,0);
			break;
		}
		case 5:	/* Date */
			st->recorddate.weekday	= scode[0]&0xf;
			st->recorddate.month	= scode[2];
			st->recorddate.year	= scode[1];
			st->recorddate.day	= scode[3];
			st->recorddate.hour	= scode[4];
			st->recorddate.minute	= scode[5];
			st->recorddate.second	= scode[6];
			st->values |= DA_V_HAVE_RDATE;
			break;
		case 6:{/* Katalognr */
			int	j;
			st->catalog.nr[0]='0'+(scode[0]&0xf);
			for (j=2;j<12;j+=2)
				st->catalog.nr[j]='0'+(scode[j/2]&0xf);
			for (j=1;j<12;j+=2)
				st->catalog.nr[j]='0'+((scode[j/2]&0xf0)>>4);

			break;
		}
		case 7:	/* iscs nr */
			/* dunno if this is correct */
			/* SHOW me a tape with ISRC on it please :) */
			switch(scode[0]&0xf) {
			case 0: /* 00 00 */
				st->isrc.country=scode[1]*64+scode[2];
				st->isrc.producer=scode[3]*64*64+scode[4]*64+scode[5];
				break;
			case 4: /* 01 00 */
				st->isrc.year=scode[1];
				st->isrc.serialnr=scode[2]*1000+scode[3]*10+(scode[4]>>4);
				break;
			case 8:{/* 10 00 , MARCUS */
				int	curtrack,len;
				int	j;

				curtrack=(scode[1]<<8)+scode[2];
				if (	(curtrack!=st->_track_curtrack) ||
					(!scode[3])
				) {
					if (scode[3])
						break;
					if (st->_track_name) {
						for (j=0;j<st->_track_curlen;j++)
							if (!st->_track_name[j])
								break;
						/* valid string ?*/
						if (j>=st->_track_curlen-2) {
							da_add_trackname(st,st->_track_curtrack,st->_track_name,0);
						}
					}
					st->_track_curtrack	= curtrack;
					len=(scode[4]<<8)+scode[5];
					st->_track_name		= da_alloc(len+3);
					st->_track_curlen	= len;
					memset(st->_track_name,'\0',len);
					st->_track_name[0]=scode[6];
				} else {
					if (!scode[3]) {
						st->_track_name[0]=scode[6];
					} else {
						len=scode[3]*3-2;
						if (len+2>st->_track_curlen)
							break;
						memcpy(st->_track_name+len,scode+4,3);
					}
				}
				break;
			}
			case 9: {/*MARCUS, current trackname */
				if (!st->_curtrackname) {
					st->_curtrackname = malloc((scode[1]+1)*5+1);
					st->_curtracknamelen = (scode[1]+1)*5+1;
				} else {
					if (st->_curtracknamelen < (scode[1]+1)*5+1) {
						st->_curtrackname = realloc(
							st->_curtrackname,
							(scode[1]+1)*5+1
						);
						st->_curtracknamelen = (scode[1]+1)*5+1;
					}
				}
				memcpy(st->_curtrackname+scode[1]*5,scode+2,5);
				if (!scode[6]) {/* end of string */
					if (st->curtrackname)
						free(st->curtrackname);
					st->values|=DA_V_TRACKNAME;
					st->curtrackname = strdup(st->_curtrackname);
				}
				break;
			}
			case 0xa: {
				st->curtracktime.indexnr	= bcd2l(scode[2]); 
				st->curtracktime.hour	= bcd2l(scode[3]); 
				st->curtracktime.minute	= bcd2l(scode[4]); 
				st->curtracktime.second	= bcd2l(scode[5]); 
				st->curtracktime.frame	= bcd2l(scode[6]); 
				st->values |= DA_V_TRACKTIME;
				break;
			}
			default:break;
			}
		default:break;
		}
	}

	if (	_CMASK(7) && 
		((subid[1]&0xF0)!=0xA0) && (subid[2]!=0xAA) &&
		(subid[1]&0xF0!=0) && (subid[2]!=0)
	)
		st->prognr=((subid[1]&0xf0)<<4)+subid[2];
	if (_CMASK(4)) {
		/* registered in TOC */
		if (!(st->values&DA_V_INTOC)) {
			st->cnt_toc=0;
			st->values|=DA_V_INTOC;
		}
		st->cnt_toc++;
	} else {
		st->cnt_toc=0;
		st->values&=~DA_V_INTOC;
	}
	if (_CMASK(5)) {
		/* jumpzone */
		if (!(st->values&DA_V_INJUMP)) {
			st->cnt_jump=0;
			st->values|=DA_V_INJUMP;
		}
		st->cnt_jump++;
		if (st->cnt_jump>=300)
			st->values|=DA_V_JUMPZONE;
	} else {
		st->cnt_jump=0;
		st->values&=~DA_V_INJUMP;
	}
	if (_CMASK(6)) {
		if (!(st->values&DA_V_INSTART)) {
			st->cnt_start=0;
			st->values|=DA_V_INSTART;
			st->values&=~DA_V_JUMPZONE;
		}
		st->cnt_start++;
		/* startzone */
	} else {
		st->cnt_start=0;
		st->values&=~DA_V_INSTART;
	}
	if (st->values&DA_V_JUMPZONE)
		st->mute=1;
	st->subid.channels	=MASK(0,0);
		if (st->subid.channels != DA_SUBID_CHANNELS_2)
			st->mute=1;
	st->subid.samplerate		=MASK(0,2);
	if (st->subid.samplerate == 3)
		st->mute=1;
	st->subid.emphasis	=MASK(0,4);
	st->subid.datapacket	=MASK(1,0);
	st->subid.scms		=MASK(1,2);
	st->subid.width		=MASK(1,4);
	st->subid.encoding	=MASK(1,6);
	da_check_callbacks(st);
}
#undef	MASK
#undef	_CMASK

/* internal callbacks check helper function */
void
da_check_callbacks(struct da_state *st) {
	struct	da_time	*xtime=NULL;
	union	da_callback_data	data;
	struct	da_callback		*cb;
	int	i,mask=0,docb;

	cb=st->cb;
	while (cb) {
		docb=0;
		/* getting rid of goto's or worse hacks in the next switch */
		switch (cb->type) {
		case DA_CB_PROGTIME:
			xtime=&(st->progtime);
			mask=DA_V_HAVE_PTIME;
			break;
		case DA_CB_RUNTIME:
			xtime=&(st->runtime);
			mask=DA_V_HAVE_RTIME;
			break;
		case DA_CB_ABSTIME:
			xtime=&(st->abstime);
			mask=DA_V_HAVE_ATIME;
			break;
		case DA_CB_RECORDDATE:
			mask=DA_V_HAVE_RDATE;
			break;
		case DA_CB_TRACKTIME:
			xtime=&(st->curtracktime);
			mask=DA_V_TRACKTIME;
			break;
		default:/* EMPTY */
			break;
		}
		switch (cb->type) {
		case DA_CB_SUBID:
			if (memcmp(&(cb->data.subid),&(st->subid),sizeof(st->subid))) {
				memcpy(&(cb->data.subid),&(st->subid),sizeof(st->subid));
				memcpy(&(data.subid),&(st->subid),sizeof(st->subid));
				docb=1;
			}
			break;
		case DA_CB_SAMPLERATE:
			if (cb->data.samplerate!=da_real_samplerate(st)) {
				cb->data.samplerate=da_real_samplerate(st);
				data.samplerate=da_real_samplerate(st);
				docb=1;
			}
			break;
		case DA_CB_TRACKNAME:
			if (	(	(cb->data.trackname==NULL) &&
					st->curtrackname
				) || (	st->curtrackname == NULL &&
					cb->data.trackname
				) || (	st->curtrackname &&
					cb->data.trackname &&
					strcmp(st->curtrackname,cb->data.trackname)
				) ||
				(cb->lasttimevalid!=(st->values&DA_V_TRACKNAME))
			) {
				if (cb->data.trackname)
					free(cb->data.trackname);
				cb->data.trackname=strdup(st->curtrackname);
				data.trackname=st->curtrackname;
				cb->lasttimevalid = st->values & DA_V_TRACKNAME;
				docb=1;
			}
			break;
		case DA_CB_CHANNELS:
			if (cb->data.channels!=da_real_channels(st)) {
				cb->data.channels=da_real_channels(st);
				data.channels=da_real_channels(st);
				docb=1;
			}
			break;
		case DA_CB_PROGNR:
			if (cb->data.prognr!=st->prognr) {
				cb->data.prognr=st->prognr;
				data.prognr=bcd2l(st->prognr);
				docb=1;
			}
			break;
		case DA_CB_TOC:
			if (cb->data.toc.tocentries!=st->tocentries)
				docb=1;
			else {
				for (i=st->tocentries;i--;) {
#define MATCH(x) \
	if (st->toc[i].x!=cb->data.toc.toc[i].x) {\
		/*fprintf(stderr,"mismatching "#x" %d-%d in element %d.\n",st->toc[i].x,cb->data.toc.toc[i].x,i);*/\
		docb=1;\
		break;\
	}
					MATCH(prognr);
					MATCH(value);
					MATCH(hour);
					MATCH(minute);
					MATCH(second);
					MATCH(frame);
					if (st->toc[i].name)
						if (cb->data.toc.toc[i].name==NULL) {
							docb=1;
						} else {
							docb=strcmp(st->toc[i].name,cb->data.toc.toc[i].name);
						}
				}
			}
			if (st->values&DA_V_TOC_CHANGED) {
				docb=1;
				st->values &= ~DA_V_TOC_CHANGED;
			}
			if (docb) {
				cb->data.toc.toc	= da_realloc(cb->data.toc.toc,st->tocentries*sizeof(struct da_tocentry));
				for (i=cb->data.toc.tocentries;i<st->tocentries;i++)
					memset(&(cb->data.toc.toc[i]),'\0',sizeof(cb->data.toc.toc[i]));
				cb->data.toc.tocentries	= st->tocentries;
				for (i=st->tocentries;i--;) {
					cb->data.toc.toc[i].prognr=st->toc[i].prognr;
					cb->data.toc.toc[i].value=st->toc[i].value;
					cb->data.toc.toc[i].hour=st->toc[i].hour;
					cb->data.toc.toc[i].minute=st->toc[i].minute;
					cb->data.toc.toc[i].second=st->toc[i].second;
					cb->data.toc.toc[i].frame=st->toc[i].frame;
					if (cb->data.toc.toc[i].name)
						free (cb->data.toc.toc[i].name);
					cb->data.toc.toc[i].name=NULL;
					if (st->toc[i].name)
						cb->data.toc.toc[i].name=strdup(st->toc[i].name);
				}
				data.toc.tocentries= st->tocentries;
				data.toc.toc=da_alloc(st->tocentries*sizeof(struct da_tocentry));
				for (i=st->tocentries;i--;) {
					data.toc.toc[i].prognr	=st->toc[i].prognr;
					data.toc.toc[i].value	=st->toc[i].value;
					data.toc.toc[i].hour	=st->toc[i].hour;
					data.toc.toc[i].minute	=st->toc[i].minute;
					data.toc.toc[i].second	=st->toc[i].second;
					data.toc.toc[i].frame	=st->toc[i].frame;
					if (st->toc[i].name)
						data.toc.toc[i].name	=strdup(st->toc[i].name);
					else
						data.toc.toc[i].name	=NULL;
				}
				docb=1;
			}
			break;
		case DA_CB_PROGTIME:
		case DA_CB_ABSTIME:
		case DA_CB_RUNTIME:
			switch (cb->granularity) {
			case DA_CB_FRAMES:
				if (cb->data.time.frame!=xtime->frame)
					docb=1;
			case DA_CB_SECONDS:
				if (cb->data.time.second!=xtime->second)
					docb=1;
			case DA_CB_MINUTES:
				if (cb->data.time.minute!=xtime->minute)
					docb=1;
			case DA_CB_HOURS:
				if (cb->data.time.hour!=xtime->hour)
					docb=1;
			case DA_CB_INDEXNR:
				if (cb->data.time.indexnr!=xtime->indexnr)
					docb=1;
			default:
				break;
			}
			if ((mask&st->values)!=cb->lasttimevalid)
				docb=1;
			cb->lasttimevalid=mask&st->values;
			if (!(mask&st->values))
				docb=-1;
			if (docb)
				memcpy(&(cb->data.time),xtime,sizeof(struct da_time));
			data.time.frame=bcd2l(cb->data.time.frame);
			data.time.second=bcd2l(cb->data.time.second);
			data.time.minute=bcd2l(cb->data.time.minute);
			data.time.hour=bcd2l(cb->data.time.hour);
			data.time.indexnr=bcd2l(cb->data.time.indexnr);
			break;
		case DA_CB_RECORDDATE:
			switch (cb->granularity) {
			case DA_CB_SECONDS:
				if (cb->data.date.second!=st->recorddate.second)
					docb=1;
			case DA_CB_MINUTES:
				if (cb->data.date.minute!=st->recorddate.minute)
					docb=1;
			case DA_CB_HOURS:
				if (cb->data.date.hour!=st->recorddate.hour)
					docb=1;
			case DA_CB_WEEKDAYS:
				if (cb->data.date.weekday!=st->recorddate.weekday)
					docb=1;
			case DA_CB_DAYS:
				if (cb->data.date.day!=st->recorddate.day)
					docb=1;
			case DA_CB_MONTHS:
				if (cb->data.date.month!=st->recorddate.month)
					docb=1;
			case DA_CB_YEARS:
				if (cb->data.date.year!=st->recorddate.year)
					docb=1;
			default: /* should not happen */
				break;
			}
			if ((mask&st->values)!=cb->lasttimevalid)
				docb=1;
			cb->lasttimevalid=mask&st->values;
			if (!(mask&st->values))
				docb=-1;
			if (docb)
				memcpy(&(cb->data.date),&(st->recorddate),sizeof(struct da_date));
			data.date.second=bcd2l(cb->data.date.second);
			data.date.minute=bcd2l(cb->data.date.minute);
			data.date.hour=bcd2l(cb->data.date.hour);
			data.date.day=bcd2l(cb->data.date.day);
			data.date.weekday=bcd2l(cb->data.date.weekday);
			data.date.month=bcd2l(cb->data.date.month);
			data.date.year=bcd2l(cb->data.date.year);
			break;
		default:break;
		}
		if (docb) {
			if (docb>0)
				da_down_callback(st->id,cb->id,&data);
			else
				da_down_callback(st->id,cb->id,NULL);

		}
		cb=cb->next;
	}
}
#define	MASK(byte,bit,to) (mainid[byte]|=to<<bit)
#define	_CMASK(x,y) subid[0]|=(y<<x)

/* internal subcode/subid/mainid encoding helper function */
void
da_encode_subblock(struct da_state *st,unsigned char *buf) {
	unsigned char	*scode=buf+DATASIZE;
	unsigned char	*subid=scode+7*8;
	unsigned char	*mainid=subid+4;
	int		scnr,i;

/*printf("buf=%x,scode=%x,subid=%x,mainid=%x\n",buf,scode,subid,mainid);*/
	mainid[0]=mainid[1]=0;
	subid[0]=subid[1]=subid[2]=subid[3]=0;
	MASK(0,0,st->subid.channels);
	MASK(0,2,st->subid.samplerate);
	MASK(0,4,st->subid.emphasis);
	MASK(1,0,st->subid.datapacket);
	MASK(1,2,st->subid.scms);
	MASK(1,4,st->subid.width);
	MASK(1,6,st->subid.encoding);
	subid[0]=0; /* lower 4 bits are 0 */
	scnr=0;
	/* programtime *//* REQUIRED */
	scode[0]=(1<<4)|(st->prognr>>8);
	scode[1]=st->prognr&0xff;
	scode[2]=st->progtime.indexnr;
	scode[3]=st->progtime.hour;
	scode[4]=st->progtime.minute;
	scode[5]=st->progtime.second;
	scode[6]=st->progtime.frame;
	scode[7]=scode[0]^scode[1]^scode[2]^scode[3]^scode[4]^scode[5]^scode[6];
	scode+=8;
	/* absolute time *//* REQUIRED */
	scode[0]=(2<<4)|(st->prognr>>8);
	scode[1]=st->prognr&0xff;
	scode[2]=st->abstime.indexnr;
	scode[3]=st->abstime.hour;
	scode[4]=st->abstime.minute;
	scode[5]=st->abstime.second;
	scode[6]=st->abstime.frame;
	scode[7]=scode[0]^scode[1]^scode[2]^scode[3]^scode[4]^scode[5]^scode[6];
	scode+=8;
	/* record time *//* set automagically if not set */
	scode[0]=(3<<4)|(st->prognr>>8);
	scode[1]=st->prognr&0xff;
	scode[2]=st->abstime.indexnr;
	scode[3]=st->abstime.hour;
	scode[4]=st->abstime.minute;
	scode[5]=st->abstime.second;
	scode[6]=st->abstime.frame;
	scode[7]=scode[0]^scode[1]^scode[2]^scode[3]^scode[4]^scode[5]^scode[6];
	scode+=8;
	/* record date*/
	/* set automagically if not set */
	scode[0]=(5<<4)|(st->recorddate.weekday&0xf);
	scode[1]=st->recorddate.year;
	scode[2]=st->recorddate.month;
	scode[3]=st->recorddate.day;
	scode[4]=st->recorddate.hour;
	scode[5]=st->recorddate.minute;
	scode[6]=st->recorddate.second;
	scode[7]=scode[0]^scode[1]^scode[2]^scode[3]^scode[4]^scode[5]^scode[6];
	scode+=8;
	/* toc? */
	if (st->values & DA_V_HAVE_CATALOG && st->catalog.nr[0]) {
		scode[0]=(6<<4)|((st->catalog.nr[0]-'0')&0xf);
		for (i=1;i<8;i++) scode[i]=0;
		for (i=2;i<12;i+=2)
			scode[i/2]=(st->catalog.nr[i]);
		for (i=3;i<12;i+=2)
			scode[i/2]|=(st->catalog.nr[i])<<4;
		scode[7]=scode[0]^scode[1]^scode[2]^scode[3]^scode[4]^scode[5]^scode[6];
		scode+=8;
	}
	if (st->bnr&2) {
		if (st->values & DA_V_HAVE_ISRC) { /* optional */
			if (st->bnr&1) {
				scode[0]=(7<<4)|0;
				scode[1]=st->isrc.country>>6;
				scode[2]=st->isrc.country&0x3f;
				scode[3]=st->isrc.producer>>12;
				scode[4]=(st->isrc.producer>>6)&0x3f;
				scode[5]=(st->isrc.producer)&0x3f;
			} else {
				scode[0]=(7<<4)|1;
				scode[1]=st->isrc.year;
				scode[2]=st->isrc.serialnr/1000;
				scode[3]=(st->isrc.serialnr/10)&0xff;
				scode[4]=(st->isrc.serialnr&0xf)<<4;
			}
			scode[7]=scode[0]^scode[1]^scode[2]^scode[3]^scode[4]^scode[5]^scode[6];
			scode+=8;
		}
	} else {
		if (	st->nroftracknames || 
			(st->values& (DA_V_TRACKNAME|DA_V_TRACKTIME) )
		) {
			int	nrofspecinfo=0;
			if (st->nroftracknames)
				nrofspecinfo++;
			if (st->values & DA_V_TRACKNAME)
				nrofspecinfo++;
			if (st->values & DA_V_TRACKTIME)
				nrofspecinfo++;
			nrofspecinfo = st->bnr % nrofspecinfo;
			if (st->nroftracknames) {
				if (!nrofspecinfo) {
					if (st->_track_name==NULL) {
						st->_track_curtrack=(st->_track_curtrack+1)%
									st->nroftracknames;
						st->_track_name=strdup(st->trackname[st->_track_curtrack].name);
						/* dump first block */
						scode[0]=(0x7<<4)|0x8;
						scode[1]=st->trackname[st->_track_curtrack].prognr>>8;
						scode[2]=st->trackname[st->_track_curtrack].prognr&0xff;
						scode[3]=0; /* block 0 */
						scode[4]=st->trackname[st->_track_curtrack].namelen>>8;
						scode[5]=st->trackname[st->_track_curtrack].namelen&0xff;
						scode[6]=st->_track_name[0];
						st->_track_curlen=1;
						if (!st->_track_name[0]) {
							free(st->_track_name);
							st->_track_name=NULL;
						}
					} else {
						char	*s;

						s=st->_track_name+st->_track_curlen;
						scode[0]=(0x7<<4)|0x8;
						scode[1]=st->trackname[st->_track_curtrack].prognr>>8;
						scode[2]=st->trackname[st->_track_curtrack].prognr&0xff;
						scode[3]=(st->_track_curlen-1)/3+1;
						scode[4]=*s;
						if (!*s) {
							scode[5]=scode[6]=0;
						} else {
							s++;
							scode[5]=*s;
							if (!*s)
								scode[6]=0;
							else {
								s++;
								scode[6]=*s;
							}
						}
						if (!*s) {
							free(st->_track_name);
							st->_track_name=NULL;
							st->_track_curlen=0;
						} else
							st->_track_curlen+=3;
					}
				} else 
					nrofspecinfo--;
			}
			if (st->values & DA_V_TRACKNAME) {
				if (	!st->_curtrackname ||
					(strcmp(st->_curtrackname,st->curtrackname))
				) {
					if (st->_curtrackname)
						free (st->_curtrackname);
					st->_curtrackname = strdup(st->curtrackname);
				}
				if (!nrofspecinfo) {
					scode[0] = (0x7<<4) | 0x8 | 0x1;
					scode[1] = st->_curtracknamelen/5;
					scode[7] = 0;
					if (st->_curtrackname)
						strncpy((char*)scode+2,st->_curtrackname+st->_curtracknamelen,5);
					if (strlen((char*)scode+2)!=5)
						st->_curtracknamelen =0;
					else
						st->_curtracknamelen+=5;
				} else
					nrofspecinfo--;
			}
			nrofspecinfo--;
			if (st->values & DA_V_TRACKTIME) {
				if (!nrofspecinfo) {
					scode[0] = (0x7<<4)|0x8|0x2;
					scode[1] = 0;
					scode[2] = l2bcd(st->curtracktime.indexnr);
					scode[3] = l2bcd(st->curtracktime.hour);
					scode[4] = l2bcd(st->curtracktime.minute);
					scode[5] = l2bcd(st->curtracktime.second);
					scode[6] = l2bcd(st->curtracktime.frame);
				} else
					nrofspecinfo--;
			}
			scode[7]=scode[0]^scode[1]^scode[2]^scode[3]^scode[4]^scode[5]^scode[6];
			scode+=8;
		}
	}
	/* FIXME: R-TOC oder U-TOC schreiben? 
	 * zur zeit noch TOC ueber das ganze Band... 
	 * TOC handling generell zu ueberarbeiten
	 */
	if (st->tocentries) {
		/* toc.current
		 * toc.towrite
		 */
		if ((st->toctowrite--)<=0) {
			st->toccurrent=(st->toccurrent+1)%st->tocentries;
			switch (st->toc[st->toccurrent].value) {
			case DA_TOC_BB:
				st->toctowrite=66;
				break;
			case DA_TOC_EE:
				st->toctowrite=33;
				break;
			default:
				st->toctowrite=1;
				break;
			}
		}
		scode[0]=(4<<4)|(st->toc[st->toccurrent].prognr>>8);
		scode[1]=st->toc[st->toccurrent].prognr&0xff;
		scode[2]=st->toc[st->toccurrent].value;
		scode[3]=st->toc[st->toccurrent].hour;
		scode[4]=st->toc[st->toccurrent].minute;
		scode[5]=st->toc[st->toccurrent].second;
		scode[6]=st->toc[st->toccurrent].frame;
		scode[7]=scode[0]^scode[1]^scode[2]^scode[3]^scode[4]^scode[5]^scode[6];
		scode+=8;
	}
	subid[1]=7; /* 7 is for subcodes set */
	if ((st->values & DA_V_PRERECORDED)||st->values&DA_V_INSTART) {
		subid[1]|=(st->prognr>>4)|7;
		subid[2]|=st->prognr&0xff;
	}
	if (st->values&DA_V_INTOC)
		_CMASK(4,1);
	if (st->values&DA_V_INJUMP)
		_CMASK(5,1);
	if (st->values&DA_V_INSTART)
		_CMASK(6,1);
	/* only set priority ID if not 0xAA and not 0x00 */
	if (	((st->values & DA_V_PRERECORDED) ||
		 (st->values&DA_V_INSTART)
		) && 
		(st->prognr != 0xAA && st->prognr!=0)
	)
		_CMASK(7,1);
}

/* automatical advance subcode information */
void
da_advance_subcode(struct da_state *st) {
	int	flag,maxframe;

	st->bnr++;
	if (st->values&DA_V_INJUMP) {
		st->cnt_jump--;
		if (st->cnt_jump<=0)
			st->values&=~DA_V_INJUMP;
	}
	if (st->values&DA_V_INSTART) {
		st->cnt_start--;
		if (st->cnt_start<=0)
			st->values&=~DA_V_INSTART;
	}
	/* increase times. dates. etc. */
	/* progtime */
	if (st->subid.encoding==DA_SUBID_ENCODING_12)
		maxframe=0x15;
	else
		maxframe=0x32;
	da_advance_time(&(st->progtime),maxframe);
	da_advance_time(&(st->runtime),maxframe);
	da_advance_time(&(st->abstime),0x32);

	/* just increase every every maxframe(+1/3) the second */
	if (!(st->bnr % (maxframe+(!(st->recorddate.second%3))))) {
#define P st->recorddate
		do {
			bcdinc(P.second);
			if (P.second<0x60)
				break;
			P.second=0;
			bcdinc(P.minute);
			if (P.minute<0x60)
				break;
			P.minute=0;
			bcdinc(P.hour);
			if (P.hour<0x24)
				break;
			P.hour=0;
			bcdinc(P.day);
			P.weekday=(P.weekday+1)%7;
			flag=0;
			switch (P.month) {
			case 1:case 3:case 5:case 7:case 8:case 10:case 12:
				if (P.day>=0x31)
					flag=1;
				break;
			case 2:
				if (P.day>=(0x28+!(P.year%0x4)-!(P.year%0x100)+!(P.year%0x400)))
					flag=1;
				break;
			case 4:case 6:case 9:case 11:
				if (P.day>=0x30)
					flag=1;
				break;
			default:flag=1;
				break;
			}
			if (!flag)
				break;
			P.day=0;
			bcdinc(P.month);
			if (P.month<0x12)
				break;
			P.month=0;
			bcdinc(P.year);
			break;
		} while (1);
	}
}

/* automatical advance timecode */
void
da_advance_time(struct da_time *x,int maxframe) {
	bcdinc(x->frame);
	if (x->frame<(maxframe+(!(x->second%3))))
		return;
	x->frame=0;
	bcdinc(x->second);
	if (x->second<0x60)
		return;
	x->second=0;
	bcdinc(x->minute);
	if (x->minute<0x60)
		return;
	x->minute=0;
	bcdinc(x->hour);
	if (x->hour<0x100)
		return;
	x->hour=0;
}

/* automatical deadvance timecode */
void
da_deadvance_time(struct da_time *x,int maxframe) {
	if (x->frame) {
		x->frame=l2bcd(bcd2l(x->frame)-1);
		return;
	} 
	x->frame=!((x->second-1)%3)+maxframe;
	if (x->second) {
		x->second=l2bcd(bcd2l(x->second)-1);
		return;
	}
	x->second=0x59;
	if (x->minute) {
		x->minute=l2bcd(bcd2l(x->minute)-1);
		return;
	}
	x->minute=0x59;
	if (x->hour) {
		x->hour=l2bcd(bcd2l(x->hour)-1);
		return;
	}
	x->hour=0x99; /* should not happen */
}

/* compares time (BCD) with time (L) */
int
cmptime_bl(struct da_time *x,struct da_time *y) {
	int	z;

	z=bcd2l(x->indexnr)-y->indexnr;if (z) return z;
	z=bcd2l(x->hour)-y->hour;if (z) return z;
	z=bcd2l(x->minute)-y->minute;if (z) return z;
	z=bcd2l(x->second)-y->second;if (z) return z;
	z=bcd2l(x->frame)-y->frame;if (z) return z;
	return 0;
}
