// Ensoniq ESQ1/SQ80 tape format decoder
// (c)2023 by Rainer Buchty, rainer@buchty.net
//
// Encoding: biphase-mark, lsh input
//
// Data types
//	bank:	 4634 bytes	(type 01)
//	prog:	 n/a		(type 02 -- not supported)
//	one seq: ?? bytes	(type 03)
//	all seq: ?? bytes	(type 04)
//
// Format
// 	Leader tone (all mark)
// 	4x 0x7e header	(not in checksum)
//	data w/ zero-padding after 5 ones and even parity
//		uint16_t size
//		uint8_t  type
//	checksum (xor summing)
//	2x 0x7e header

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ctype.h>

#define HI 2
#define LO 1
#define ZZ 0

// format
//	hi-phase (all mark)
//	lo-phase (all space)
//	0110001

int main(int argc, char **argv)
{
	FILE *f;
	uint8_t state=ZZ, lstate=ZZ, xstate=0;
	uint8_t data, bcnt, chksum, sync, padding, pty;
	uint16_t shi, slo, szz;
	uint16_t min, thrs;
	uint16_t cnt, lcnt=0;
	uint32_t mem=0,xcnt=0;
	int ch;
		
	if(argc!=2)
	{
		printf("Need file name.\n");
		exit(0);
	}
	argv++;
	
	f=fopen(*argv,"r");
	if(!f)
	{
		printf("Can't read \"%s\".\n",*argv);
		exit(0);
	}

	
	// signal analysis
	// determine min/max/avg signal level
	szz=0;
	slo=0xff;
	shi=0x00;
	while( (ch=fgetc(f)) != EOF )
	{
		ch&=0xff;
		
		if(ch<slo) slo=(slo+ch)/2;
		if(ch>shi) shi=(shi+ch)/2;
		szz=(szz+ch)/2;
	}
	printf("Signal analysis\n\tmin: %02x\n\tmax: %02x\n\tavg: %02x\n",slo,shi,szz);
	
	printf("\nComputed thresholds\n\tlo <= %02x\n\thi >= %02x\n\n",szz-(szz-slo)/4, szz+(shi-szz)/4);

	// data analysis
	rewind(f);
	
	cnt=0;
	min=0x8000;
	while( (ch=fgetc(f)) != EOF )
	{
		ch&=0xff;
		
		// advance counter 
		cnt++;

		// sample last signal state
		lstate=state;

		// signal state defines 
		if(ch>=szz+(shi-szz)/4)			// typically >=0xa0
			state=HI;
		else if(ch<=szz-(szz-slo)/4)	// typically <=0x60
			state=LO;
		else
			state=ZZ;

		// signal transition
		// state to idle
		if( (state==ZZ) && (lstate!=ZZ) )
		{
			switch(xstate)
			{				
				// skip first transition, might take ages and mess with cnt
				case 0:
					xstate=1;
					break;
					
				// leader tone: all mark / tune to min
				case 1: 
					if(cnt<min)
						min=(min+cnt)/2;
								
					if(cnt>min+min/2)
					{
						thrs=min+min/2;
						printf("Timing analysis\n\tmin period:\t%d samples\n\tthreshold:\t%d samples\n\n",min,thrs);

						// prepare parsing
						bcnt=1;		// we already have the first bit of a byte
						data=0; 	// and it's zero
						lcnt=0;		// 1/0 parsing: start with a clean bit
						sync=4;		// expected sync bytes
						chksum=0;	// clear chksum
						padding=0;	// clear zero-counter for padding
						pty=0;		// clear parity
						xcnt=1;		// we already read one bit
						
						// this is from sync, so output it right away
						printf("%5d: %d",mem,data&1);

						// enter parsing
						xstate=2;

					}
				
					break;
				
				// now parse
				// 4x sync (1000.0001)
				// Nx data!
				// 2x sync
				default:					

					// period counter
					// lcnt must be >trsh
					lcnt+=cnt;
					
					// if this occurs, there's severe timing error
					if((cnt>=3*thrs)||(lcnt>=3*thrs))
						printf("Timing error: %d / %d\n",cnt,lcnt);
					
					// if count time=max, it's always 1
					// otherwise: if count time is min, 2x*min = 0
					if(lcnt>thrs)
					{
						uint8_t bit=(lcnt==cnt)?0:1;
						uint8_t valid=1;				// default: valid data bit

						switch(bit)
						{
							// 0-bit
							case 0:
								// don't add padding to data
								if(padding==5)
								{
									printf("*");
									valid=0;
								}	
								// clear padding counter
								padding=0;

								break;

							// 1-bit
							case 1:
								// sync has no padding
								if(sync>0)
									break;

								// if padding is pending and we have a 1, then something's wrong
								if(padding>5)
									printf("Coding error. 5-ones violation (%d).\n",padding);
									
								// true data inserts 1-bit after five 0-bits
								padding++;
			
								break;
						}	

						// byte not complete
						// add valid data to data byte
						if(bcnt<8)
						{
							// left-shift bits into byte
							if(valid==1)
							{
								data<<=1;
								data|=bit;

								pty^=bit;

								printf("%d",bit);
								printf("<%d>",pty);

								if(bcnt==3)
									printf(".");

								bcnt++;

								// 
								// this is so much more convenient ...
								if((bcnt==8)&&(sync!=0))
								{
									sync--;
									goto next;
								}
							}
						}

						// byte fully read
						// show parity, if required
						// add to chksum
						else 
						{
							if(pty==bit)
								printf("<%d>",bit);	// pty ok
							else
								printf("!%d!",bit);	// pty err
next:							
							// reset bit counter, parity, padding
							bcnt=0;
							pty=0;
							padding=0;
								
							// add data into checksum
							chksum^=data;
								
							// output data: lsh/rsh as biphase-s/biphase-m
							mem++;
							printf(
								"\t%02x [%c] %2d bits read; sync: %d\n%5d: ",
								data, 
								isalnum(data)?data:' ', 
								xcnt,
								sync, 
								mem 
							);
							xcnt=0;
						}
						
						lcnt=0; 
						xcnt++;

					}
					

					break;
				
			} // switch xstate

			// clear counter after parsing transition time
			cnt=0;
		
		} // if (state)

	} // while (fgetc)
	
	printf("\ngot %d bytes\n",mem);
	
	fclose(f);
	exit(1);
}
