// DSS1 disk converter
//
// (c)2017 Rainer Buchty, rainer@buchty.net
//
//		V0.9	2017-04-19		seems to be working, not yet tested on the real machine
//		V1.0	2017-04-20		tested working with standard and turbo disks on DSS-1
//
// Converts disk dumps to individual DS1 and DSM files for use with USB storage together with the Straylight Engineering DSS1 expansion
// Creates directory of dumpfile name (sans filename extension) and puts DS1/DSM files there
//
// supports linear dumps (dd-style), interleaved dumps (QM-style), and "turbo" disks (delta-compressed).
//
// Thanks to Tom Virostek for fruitful discussions and invaluable information.
//
// TODO: check DSM-1 disks -- do these HD disks differ other than in BAM and overall size?
// 		DSS1 disk format
//		00.0000-00.03ff	BAM
//		00.0400-00.23ff directory (128 files: max, 4 system files, 1 wavetable file, 1 MIDI-param save file, rest individual sample files)
//		00.2400-0c.7fff	data (01.8fff on HD disks, if existing)
//
//	view/edit with tab indent width 4 for maximum readability

#define _GNU_SOURCE		

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

#include <errno.h>
#include <error.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <fcntl.h>
#include <libgen.h>
#include <ctype.h>

#include <unistd.h>		// GNU: need get_current_dir_name()
#include <string.h>		// GNU: need GNU basename, not POSIX

// disk information
#define DISK_INTLV	0x01	// disk is interleaved
#define DISK_TURBO	0x02	// disk is in "turbo" format
#define DISK_UNKWN	0x80	// unknown disk

// section sizes
#define SIZE_DSS	0xc8000							// DSS1: 80 tracks, 5 sectors/track, 2 sides
#define SIZE_DSM	(2*SIZE_DSS)					// DSM1: 80 tracks, 10 sectors/track, 2 sides (presumed due to HD spec)

#define SIZE_BAM	0x400
#define SIZE_DIR	0x2000
#define SIZE_OFFS 	(SIZE_BAM+SIZE_DIR)
#define SIZE_DATA	(0xc8000-SIZE_OFFS)

#define MAX_WTB		((SIZE_DIR/64)-4-1-1)			// maximum number of WTB entries in directory -> all but LST, SAV, and 4 SPG

// file type identifiers
#define WTB (('W'<<24)|('T'<<16)|'B')
#define SPG (('S'<<24)|('P'<<16)|'G')
#define LST (('L'<<24)|('S'<<16)|'T')
#define SAV (('S'<<24)|('A'<<16)|'V')

// file type cleartext
char *tstr[]={
	"???",
	"WTB (Multisample Data)",
	"SPG (System File)",
	"LST (Wavetable List)",
	"SAV (MIDI Param Save)"
};

// characters forbidden in FAT names
char nonfat[]={"\"*+.,/:;<=>?\\[]|"};

// default MIDI parameters (for .DS1)
char midiparam[]={
	0x00, 0x00, 0x03, 	// TX channel, RX channel, Prog change mode, 
	0x01, 0x01, 0x01, 	// flags:  MIDI mod enable, MIDI aft enable, Omni mode enable
	0x00, 0x00, 0x00, 	// MIDI data slider A/B/tune slider assign
	0x00				// spare
};

// directory entry
typedef struct
{
	char name[9];		// file name (0-terminated)
	char fatname[9];	// FAT-sanitized name
	uint8_t type;		// file type
	uint8_t id;			// file ID
	uint32_t size;		// file size in sector numbers
	uint32_t len;		// file in bytes (only for WTB)
	uint32_t *offs;		// offs[numsec] provides offset to file parts
} direntry_t;

// linear (i==0) or 3-way interleaved (i==0) offset
// CQM conversions kept interleaved sector mappings
uint32_t s2o(uint16_t s, uint8_t i)
{
	uint8_t map[]= {
		 0,1,2,3,4,	// linear
		 0,2,4,1,3 	// 3-way interleave
	};
	uint8_t xs, xo;

	xs = s%5;
	xo = s/5;
	
	return (xo*5 + map[(i&1)*5+xs])*1024;
}

// increase buffer pointer for disk files
// check for sector-size and file-length overflow (return -1)
// return positive index otherwise
int32_t buf_inc(uint32_t *cnt, direntry_t *d, uint8_t turbo)
{
	uint32_t offs;

	if(
		((*cnt)/1024 >   d->size) 		// sector overrun
	)
		return -1;

	(*cnt)++;

	// turbo files are assumed linear
	if(turbo==1)
		offs=d->offs[0] + (*cnt);

	// standard files traverse link list
	else
		offs=d->offs[(*cnt)/1024] + ((*cnt)&1023);
	
	return offs;
}	
	
// create link list as offset based on individual track/sector lists and interleave flag
void getoffs(uint8_t *dir, char *name, uint8_t id, uint8_t idx, uint32_t *list, uint8_t ileave)
{
	uint32_t i,j;
	uint32_t ns,s,t;

	// traverse maximum directory area
	for(i=0; i<SIZE_DIR; i+=0x40)
	{
		if(
			(dir[i]==0x00) 	&&					// don't fall for empty directory area
			(dir[i+2]==id)	&&					// match file ID
			(memcmp(&dir[i+3],name,8)==0) &&	// file ID not always unique -> also use name
			(dir[i+1]==(idx&0xff))				// match currently searched index
		)
		{

			// number of sectors to resolve from current id/idx
			ns=dir[i+0x0e];
			if(ns>0x18)		
			{
				if(ns>0x19)
					fprintf(stderr,"Illegal sector length denominator. File might be corrupt.\n");
				ns=0x18;
			}
			
			// traverse sector/track list
			for(j=0; j<ns; j++)
			{
				s=(dir[i+0x10+2*j]&0x7f)-1;

				// 0x80 flags head=1 -> sector offset +5
				if(dir[i+0x10+2*j]&0x80)
					s+=5;
								
				t=dir[i+0x11+2*j];

				// generate linear offset -- untwiddle sector interleaving, if present
				list[24*idx+j]=s2o((t*10+s), ileave);
			}

			// done with id.idx, no need for further traversion
			break;
		}
	}
}

// determine file size in sectors (standard disks)
uint32_t getsize(uint8_t *dir, char *name, uint8_t id)
{
	int32_t maxidx=-1;
	uint32_t size=0, i;
	
	for(i=0; i<SIZE_DIR; i+=0x40)
	{
		if(
			(dir[i+2]==id)	&&
			(memcmp(&dir[i+3],name,8)==0)
		)
		{
			if(maxidx<dir[i+1])
			{
				maxidx=dir[i+1];
				size=dir[i+0x0e]+maxidx*0x18;
			}
		}
	}

	return size;
}

int main(int argc, char **argv)
{
	FILE *f;

	uint8_t
		disk_mode=0,	// disk mode
		
		*dbuf=NULL,		// disk buffer
		*bam, 			// direct pointer to BAM 		(&dbuf[0])	
		*dir, 			// direct pointer to directory 	(&dbuf[0x400])
		*ptr;			// generic pointer
		
	direntry_t 
		*dfile=NULL;	// directory structure

	uint32_t
		maxfile=0; 		// file counter for directory structure

	// direct access: offsets to dir[]
	int32_t 
		pos_SPG[4] = {-1}, 			// max 4 per definition
		pos_LST[1] = {-1}, 			// max 1 per definition
		pos_SAV[1] = {-1},			// max 1 per definition
		pos_WTB[MAX_WTB] = {-1};	// all but SPG, LST, SAV

	uint8_t
		cnt_LST=0,
		cnt_SPG=0,
		cnt_SAV=0,
		cnt_WTB=0;
	
	uint8_t
		verbose;
		
	uint32_t i,j;
	if(argc<2) 
		goto usage;
		
	int32_t opt;
	while ((opt=getopt(argc,argv,"vV")) !=-1)
	switch(opt)
	{
		case 'v':
			verbose|=0x01;
			break;
		case 'V':
			verbose|=0x03;
			break;
		default:
usage:
			fprintf(stderr,"\nanadisk -- a Korg DSS-1 disk analysis and conversion utility\n");
			fprintf(stderr,"\tAnalyzes DSS-1 disk dumps (dd style) and converts them to .DS1/.DSM files\n\tfor use with the Straylight Engineering expansion.\n");
			fprintf(stderr,"\t(c)2017 by Rainer Buchty, rainer@buchty.net\n\n");
			fprintf(stderr,"Usage: anadisk [-v|-V] filename\n");
			fprintf(stderr,"\t-v\tshow diagnostic output\n");
			fprintf(stderr,"\t-V\tshow diagnostic output and BAM\n\n");
			goto leave;
			break;
	}
	
	argv+=argc-1;
	f=fopen(*argv,"r");
	if(!f)
	{
		fprintf(stderr,"File %s not found.\n",*argv);
		goto leave;
	}
		
	// make room, make room
	dbuf=(uint8_t *)malloc(SIZE_DSS);
	if(!dbuf)
	{
		fprintf(stderr,"Disk buffer: out of memory.\n");
		goto leave;
	}
	ptr=dbuf;

	// get BAM
	if (fread(ptr,1,SIZE_BAM,f) != SIZE_BAM)
	{
		fprintf(stderr,"Short BAM, exiting.\n");
		goto leave;
	}
	bam=ptr;
	ptr+=SIZE_BAM;
	
	// DSM1 disks are probably not different, just more space for data, but we don't know ...
	if(dbuf[0xa0]!=0x00)
	{
		fprintf(stderr,"DSM1 (HD) disk detected -- not supported.\n");
		goto leave;
	}

	// get directory blocks
	if(fread(ptr,1,SIZE_DIR,f) != SIZE_DIR)
	{
		fprintf(stderr,"Short directory, exiting.\n");
		goto leave;
	}
	dir=ptr;
	ptr+=SIZE_DIR;

	// acquire remaining data		
	if(fread(ptr,1,SIZE_DATA,f) != SIZE_DATA)
	{
		fprintf(stderr,"Short data section, exiting.\n");
		goto leave;
	}
	
	fclose(f);

	// sanity check: interleaved disk?
	// first directory sector unformatted -> interleaved dump
	uint32_t *tst;
	tst=(uint32_t *)&dbuf[0x2400];
	if(*tst==0xe5e5e5e5)
		disk_mode|=DISK_INTLV;

	// official disk flag checking
	if(verbose&0x01) fprintf(stderr,"Disk type:\t");
	switch(dbuf[0x380])
	{
					// turbo disks
					//	-> no sector interleave
					//	-> no link list in directory but start sector followed by size in sectors; files are linear on disk
					//	-> data with delta-compression on cd byte
		case 0x0e:	if(verbose&0x01) fprintf(stderr,"turbo");
					disk_mode|=DISK_TURBO;
					break;
					
					// standard disks
					//	-> 3-way interleave (1-3-5-2-4) -- but is this always in the dump?
					//	-> link list in directory (potentially spanning multiple indexes)
					//	-> packed 12-bit data (3 bytes ab cd ef -> 2 words ab cc, ef dd)
		case 0x0b:	if(verbose&0x01) fprintf(stderr,"standard");
					// could be a proper linear image, so rely on interleave checking above
					break;
					
		default:	if(verbose&0x01) fprintf(stderr,"unknown (%02x)",dbuf[0x380]);
					// could be a proper linear image, so rely on interleave checking above
					disk_mode|=DISK_UNKWN;
					break;
	}

	if(verbose&0x01) fprintf(stderr,"\nInterleave:\t%s",(disk_mode&DISK_INTLV)?"yes":"no");
	

	// BAM rendering
	if(verbose&0x03)
	{
		uint8_t t=0;
		fprintf(stderr,"\n\t\t\tSector  Map\n");
		fprintf(stderr,"\t\t\tSide0 Side1\n");
		fprintf(stderr,"Track\tOffset\t\t12345.12345");
	
		for(i=0; i<0xa0; i++)
		{
			if(!(i&1))
			{
				fprintf(stderr,"\n%02d\t[%02x.%04x]\t",t,(t*10240)>>16,(t*10240)&0xffff);
				t++;
			}

			if(bam[i]&0xe0)
			{
				for(j=0;j<5;j++)
					fprintf(stderr,"%c",(bam[i]&(1<<j))?'*':'-');
				fprintf(stderr," ");
			}
		}
		fprintf(stderr,"\n");
	}
	

	// traverse directory
	// 00: 		00 = valid entry
	// 01: 		multipart entry index counter
	// 02: 		file ID
	// 03-0d:	file name
	// 0e:		size (in sectors) or 0x19 for signaling further sector block
	// 0f:		0x00 marker (or part of 16-bit size)
	// 10-3f:	sector/track list (sector|0x80 -> head 1)
	for(i=0; i<SIZE_DIR; i+=0x40)
	{
		// valid entry and first part of file
		if(
			(dir[i] 	 == 0x00) &&	// no unformatted sectors
			(dir[i+0x01] == 0x00) &&	// index = 0
			(dir[i+0x0e] != 0x00)		// length > 0
		)
		{
			maxfile++;
			
			// add another file
			dfile=(direntry_t *)realloc(dfile,maxfile*sizeof(direntry_t));
			if(!dfile)
			{
				fprintf(stderr,"Out of memory (dfile)\n");
				goto leave;
			}

			// pointer to active file
			direntry_t *d=&dfile[maxfile-1];
			
			// init new file
			memset(d,sizeof(direntry_t),0);
			
			// get basic properties: name, id, size
			memcpy(&(d->name), &(dir[i+3]), 8);
			d->name[8]=0x00;
			d->id 		= dir[i+2];

			if(disk_mode&DISK_TURBO)
				d->size=(dir[i+0x13]<<8)|dir[i+0x12];
			else
				d->size 	= getsize(dir,d->name,d->id);

			// create (emtpy) offset list
			if(! (d->offs=calloc(sizeof(uint32_t),d->size)) )
			{
				fprintf(stderr,"Out of memory (d->offs).\n");
				goto leave;
			}

			// parse file type for later use
			switch( (dir[i+0x0b]<<24) | (dir[i+0x0c]<<16) | dir[i+0x0d])
			{
				case WTB:	d->type=1; if(cnt_WTB<MAX_WTB) 	pos_WTB[cnt_WTB++]=maxfile-1; else fprintf(stderr,"Exceeded number of WTB files.\n"); break;
				case SPG:	d->type=2; if(cnt_SPG<  4) 		pos_SPG[cnt_SPG++]=maxfile-1; else fprintf(stderr,"Exceeded number of SPG files.\n"); break;
				case LST:	d->type=3; if(cnt_LST<  1) 		pos_LST[cnt_LST++]=maxfile-1; else fprintf(stderr,"Exceeded number of LST files.\n"); break;
				case SAV:	d->type=4; if(cnt_SAV<  1) 		pos_SAV[cnt_SAV++]=maxfile-1; else fprintf(stderr,"Exceeded number of SAV files.\n"); break;
				default:	sprintf(tstr[0],"%c%c%c",dir[i+0x0b],dir[i+0x0c],dir[i+0x0d]);
							d->type=0;
							break;
			}

			if(verbose&0x01)
			{
				fprintf(stderr,"\n\nFile ID: %02x\n",d->id);
				fprintf(stderr,"\tName:\t%s\n",d->name);
				fprintf(stderr,"\tType\t%s\n",tstr[d->type]);
				fprintf(stderr,"\tSize:\t%02d sectors (%6d bytes)\n",d->size,d->size*1024);
			}

			// dereference (potentially multi-part) track/sector list
			// turbo disks get only first offset from directory and walk linearly
			if(disk_mode&DISK_TURBO)
			{
				getoffs(dir,d->name,d->id,0,d->offs,0);

				for(j=1; j<d->size; j++)
					d->offs[j]=d->offs[0]+j*1024;
			}		

			// standard disks get all offsets from directory
			else
			{
				for(j=0; j<d->size; j+=24)
					getoffs(
						dir,
						d->name,
						d->id,
						j/24,
						d->offs,
						disk_mode&DISK_INTLV
					);
			}

			// show file link list
			if(verbose&0x01) 
			{
				fprintf(stderr,"\tLink:\t");
				for(j=0; j<d->size; j++)
				{
					fprintf(stderr,"%02x.%04x ",d->offs[j]>>16,d->offs[j]&0xffff);
					if((j%8)==7) fprintf(stderr,"\n\t\t");
				}
				if((j%8)!=7) fprintf(stderr,"\n");
			}
		}
	}

	if(verbose&0x01) fprintf(stderr,"\nDisk contains %d files.\n",maxfile);

	// FAT32 name conversion
	// 1) Sanitize WTB file names
	for(i=0; i<cnt_WTB; i++)
	{
		// init
		strcpy(dfile[pos_WTB[i]].fatname,dfile[pos_WTB[i]].name);

		j=8;
		do
		{
			j--;
			
			char *c=&(dfile[pos_WTB[i]].fatname[j]);

			// FAT32 is not case sensitive, capitalize name
			*c=toupper(*c);

			// convert trailing spaces
			if(isspace(*c))
				*c='_';
			
			// numbers and characters are safe, just check symbols
			else if(ispunct(*c))
			{
				uint8_t k;
				for(k=0; k<strlen(nonfat); k++)
				{	
					if(*c==nonfat[k])
						*c='_';
				}
			}

		} while(j>0);
	}

	// FAT32 name conversion
	// 2) Fix name clashes resulting from renaming
	// simple algorithm: max 128 files -> resolve name collisions as name[0-5]+hex-counter
	for(i=0; i<cnt_WTB; i++)
	{
		char *s1=dfile[pos_WTB[i]].fatname;
		uint8_t ccnt=0;

		// does current name collide with later names?
		for(j=0; j<cnt_WTB; j++)
		{
			char *s2=dfile[pos_WTB[j]].fatname;

			if(i!=j)
			{
				// collision found, blame later name
				if(strcmp(s1,s2)==0)
				{
					// kill last two chars
					*(s2+6)=0;
					*(s2+7)=0;

					// update last two chars with counter
					sprintf(s2,"%s%02X",s2,ccnt++);
				}
			}
		}		
	}
	
	// FAT32 name conversion
	// 3) remove trailing underscores
	for(i=0; i<cnt_WTB; i++)
	{
		char *c=dfile[pos_WTB[i]].fatname;

		j=8;
		do
		{
				j--;

				if(c[j]!='_')
					break;
				else	
					c[j]=0;

		} while(j>0);
	}
	
	// generate DS1 file
	// 0x400	all names 	(offs[x][0])
	// 0x01		number of multi-sounds added up (byte)
	// 0x2c0	all links 	(wtbl)
	// 0x2800	all refs	(offs[x][1]/[2]/[3])
	// 0x0a		midi params

	// create output directory
	char *dname=(char *)basename(*argv);

	// sanitize file name: get rid of extension
	for(i=strlen(dname)-1; i>0; i--)
		if(dname[i]=='.') { dname[i]=0; break; }

	DIR *odir=opendir(dname);
	if(!odir)
	{
		// no directory there, create it
		if(errno==ENOENT)
		{
			if(mkdir(dname,0777)==-1)
			{
				error(0, errno, "Cannot create directory \"%s\"\n.",dname);
				
				if(errno==EEXIST)
					fprintf(stderr,"File of same name exists.\n");

				goto leave;
			}
		}
	}
	closedir(odir);
	
	// save CWD and dive into output directory
	char *curdir=(char *)get_current_dir_name();
	chdir(dname);
	
	char *ofile=malloc(2*strlen(dname)+6);
	sprintf(ofile,"%s.DS1",dname);

	fprintf(stderr,"\nCreating %s ... ",ofile);
	f=fopen(ofile,"w");
	if(!f)
	{
		fprintf(stderr,"failed.\n");
		goto leave;
	}
	fprintf(stderr,"\n");
	
	// output names 
	for(i=0;i<cnt_SPG;i++)
	{
		fwrite(
			&dbuf[dfile[pos_SPG[i]].offs[0]],
			1, 0x100,
			f
		);
	}

	// pad name section
	// (shouldn't be necessary, but I've seen enough borked image files now)
	for(i=cnt_SPG*0x100; i<4*0x100; i++)
		fputc(0,f);
		
	// output number of multisamples (dummy)
	// to be overwritten at end when effective number is known
	fputc(0,f);
	
	// traverse multisample blocks
	uint8_t unique=0;
	i=dfile[pos_LST[0]].offs[0];	
	
	for(j=0; j<64; j++)
	{
		// create offset to file entry
		uint32_t k = (j/16) * 0xb1 + (j&0x0f) * 11 + i;	

		// get file size in samples
		uint32_t s=dbuf[k+9] | (dbuf[k+10]<<8) | (dbuf[k+11]<<16);
		
		// only output files with content
		if(s>0)
		{
			uint8_t found=0;

			// sort out doubles (first one is always unique)
			if(j>0)
			{
				uint32_t jj;
				for(jj=0; jj<j; jj++)
				{
					uint32_t kk = (jj/16) * 0xb1 + (jj&0x0f) * 11 + i;
					if(memcmp(&dbuf[k+1],&dbuf[kk+1],8)==0)
					{
						found=1;
						break;
					}
				}
			}
			
			// unique file -> output
			if(found==0)
			{
				unique++;

				// extract number of samples from WTB file header
				uint32_t jj;
				for(jj=0; jj<cnt_WTB; jj++)
				{
					if(memcmp(&dbuf[k+1],dfile[pos_WTB[jj]].name,8)==0)
					{
						char nbuf[11];
						
						// init name buffer with spaces
						memset(nbuf,0x20,8);

						// copy over sanitized FAT name
						sprintf(nbuf,"%s",dfile[pos_WTB[jj]].fatname);

						// add file length
						memcpy(nbuf+8,&dbuf[k+9],3);

						// render WTB file list with FAT name and file length
						fwrite(nbuf,1,11,f);

						// update sample length
						if(verbose&0x01) fprintf(stderr,"\t%s contains %06x samples.\n",dfile[pos_WTB[jj]].name,s);
						dfile[pos_WTB[jj]].len=s;

						// sanitize sample length -- number of samples must always be <= sector*1024
						if(s>dfile[pos_WTB[jj]].size*1024)
						{
							dfile[pos_WTB[jj]].len=dfile[pos_WTB[jj]].size*1024;
							fprintf(stderr,"\tEffective list longer than data provided in directory, truncating to %06x bytes. Fix image!\n\n",dfile[pos_WTB[jj]].len);
						}
							
						break;
					}
				}
			}
		}
	}

	// pad section
	// padding seems to be done differently, either all 0x00, all "?NO_MSND\0\0\0", or mixed
	for(i=0x401+unique*11; i<0x6c1; i+=11)
		fprintf(f,"?NO_MSND%c%c%c",0,0,0);

	// fill in program data
	for(i=0; i<cnt_SPG; i++)
	{
		for(j=1; j<dfile[pos_SPG[i]].size; j++)
			fwrite(
				&dbuf[dfile[pos_SPG[i]].offs[j]],
				1,((j==1)?10:11)*80,
				f
			);		
	}

	// pad data section (shouldn't be necessary)
	for(i=cnt_SPG*32; i<4*32; i++)
		fputc(0,f);

	// write midiparams
	fwrite(midiparam,1,10,f);

	// update number of multisamples
	fseek(f,0x400,SEEK_SET);
	fputc(unique,f);
	
	fclose(f);
	
	if(verbose&0x01) fprintf(stderr,"\n");
	
	// generate DSM files
	// 1) copy 0x15d header
	// 2) wave data 3->4 expanded
	// 		ab cd ef -> ab dd, ef cc in existing files 
	// 		(make it abd0 / efc0 so it's linear 12bit->16bit conversion)
	for(i=0; i<cnt_WTB; i++)
	{
		direntry_t *wtb=&dfile[pos_WTB[i]];
		
		// get length
		uint32_t l=wtb->len;
		uint32_t s=wtb->size;

		sprintf(ofile,"%s.DMS",wtb->fatname);
		fprintf(stderr,"Creating %s ...",ofile);
		if(verbose&0x01) fprintf(stderr,"%s(%3d sectors / %06x samples @ offset %06x)",(strlen(ofile)<11)?"\t\t":"\t",s,l,wtb->offs[0]);
		fprintf(stderr,"\n");
		
		// generate file
		if ( (f=fopen(ofile,"w"))==NULL )
		{
			fprintf(stderr,"\tUnable to write file, exiting.\n");
			goto leave;
		}

		// write header
		j=dfile[pos_WTB[i]].offs[0];
		fwrite(&dbuf[j],1,0x15d,f);

		// iterate through data
		j=0x15d-1;

		// init sample output and counter
		uint16_t smp_out=0x7fff;
		uint32_t smp_cnt=0;

		while(smp_cnt<dfile[pos_WTB[i]].len)
		{
			// sanitized sector / offset
			int32_t offs;

			uint8_t b1, b2;

			// turbo disks use delta compression
			if(disk_mode&DISK_TURBO)
			{
				offs=buf_inc(&j,&dfile[pos_WTB[i]],1);
				if(offs<0)
					break;
				
				b1=dbuf[offs];

				// 0x8* marks 12-bit value
				if((b1&0xf0)==0x80)
				{
					// keep MSB of 12-bit sample word
					b1&=0x0f;
					
					offs=buf_inc(&j,&dfile[pos_WTB[i]],1);
					if(offs<0)
					{
						fprintf(stderr,"\tShort data section. Padding last byte (%06x).\n",j);
						fprintf(f,"%c%c",0,b1);
						break;
					}
					
					// get LSB of 12-bit sample word and assemble output
					b2=dbuf[offs];
					smp_out=(b1<<8)|b2;
				}
				
				// other patterns are to be treated as int8_t
				else
				{
					if(b1<0x80)
						smp_out+=b1;
					else
						smp_out-=(0x100-b1);
				}
				
			}

			// standard disks use interleaved representation
			// 3 bytes AB CD EF make 2 samples ABD0 and EFC0
			else
			{						
				// get AB or EF
				offs=buf_inc(&j,&dfile[pos_WTB[i]],0);
				
				// this should never happen
				if(offs<0)
					break;

				b1=dbuf[offs];
				
				// get CD: only needs to be read once per AB EF
				if((smp_cnt&1)==0)
				{
					// this should always succeed
					offs=buf_inc(&j,&dfile[pos_WTB[i]],0);
					if(offs<0)
					{
						fprintf(stderr,"\tShort data section, padding last nibble (%06x).\n",j);
						fprintf(f,"%c%c",0,b1);
						break;
					}

					b2=dbuf[offs];

					// render abc
					smp_out = (b1<<4) | (b2&0xf0) >>4;
				} 
				
				// render efd, reuse b2 from previous round
				else
					smp_out = (b1<<4) | (b2&0x0f);
			}
			
			// output as lo/hi
			// shift 12-bit to 16-bit samples
			fprintf(f,"%c%c",(smp_out<<4)&0xff, smp_out>>4);

			smp_cnt++;

		} // while()
			
		fclose(f);
	}

	// back to where we came from
	chdir(curdir);
	free(curdir);
	
	// all went well, good food.
	i=0x600df00d;

leave:
	
	if(dbuf)	free(dbuf);
	if(dfile)	free(dfile);

	if(i==0x600df00d)
		exit(EXIT_SUCCESS);
	else
		exit(EXIT_FAILURE);
}

